﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

// using Lcs.TraceVisualiser.Forms.Components;
using Noris.Tools.TraceVisualiser.Support;
using Noris.Tools.TraceVisualiser.TraceData;
using Noris.Tools.TraceVisualiser.Components;
using Noris.Tools.TraceVisualiser.Forms;

namespace Noris.Tools.TraceVisualiser.Apollo.AplServer
{
	#region EventItem : abstraktní data jedné události v trace datech, typicky jde o párovou záležitost (řádky Begin + End v jednom záznamu Event). Bázová třída pro konkrétní eventy. Class factory.
	/// <summary>
	/// EventItem : abstraktní data jedné události v trace datech, typicky jde o párovou záležitost (řádky Begin + End v jednom záznamu Event). Bázová třída pro konkrétní eventy. Class factory.
    /// </summary>
    public abstract class EventItem : IEventItem, TraceVisualiser.Forms.IMasterDetails
    {
		#region Class factory: vytvořit potomka odpovídajícího dané události (událost je předána v třídě NorisASrvTraceItem = řádek s daty ze souboru)
		/// <summary>
		/// Vytvoří a vrátí konkrétního potomka třídy EventItem (nebo přímo instanci EventSystemItem/EventPairItem, pokud nenajde lepšího potomka), který bude odpovídat datům v daném řádku.
		/// Vrácený objekt bude již mít vyplněná data z dodaného řádku.
		/// Tato metoda nemůže řešit párovací mechanismus = tuto metodu je třeba volat pro vytvoření nové instance, když jsme pro danou věc (událost = EventItem) nenašli párový záznam.
		/// V souboru (Trace) existují jednotlivé řádky, které teprve v páru představují Event. Párovací mechanismus je zde na úrovni serveru (ServerInfo).
		/// </summary>
		/// <param name="roundtrip"></param>
		/// <param name="line"></param>
		/// <returns></returns>
		public static EventItem CreateEventItem(StringHeap stringHeap, TraceItem item)
		{
			// Kterého potomka vytvoříme?
			// Zeptáme se tříd, zda to chtějí dělat, a která se přihlásí, té to dáme:
			// Ptáme se v pořadí od nejjemnější po nejhrubší:
			if (EventSqlItem.RegisterFor(item))
				return EventSqlItem.CreateEvent(stringHeap, item);
			if (EventTransItem.RegisterFor(item))
				return EventTransItem.CreateEvent(stringHeap, item);
			if (EventCowleyItem.RegisterFor(item))
				return EventCowleyItem.CreateEvent(stringHeap, item);
			if (EventPairItem.RegisterFor(item))
				return EventPairItem.CreateEvent(stringHeap, item);
			if (EventSystemItem.RegisterFor(item))
				return EventSystemItem.CreateEvent(stringHeap, item);

			return null;
		}
		#endregion
		#region Konstrukce
        /// <summary>
        /// Kvůli technice IPlugin musím nabízet alespoň privátní bezparametrický konstruktor:
        /// </summary>
        protected EventItem() { }
        /// <summary>
        /// Standardní konstruktor
        /// </summary>
        /// <param name="stringHeap"></param>
        /// <param name="item"></param>
		protected EventItem(StringHeap stringHeap, TraceItem item)
        {
			this.StringHeap = stringHeap;
            this.Time = TimeRange.Empty;
			this._BooleanValues = 0;
            this.DetailBackColor = Color.Empty;
            this.ElementProperties = Components.ElementProperties.Selectable;
            this.FillFromItem(item);
			if (item.Keyword == "SQL")
				this.MergeKey = "";
        }
        public virtual void Dispose()
        {
            this.Trace = null;
            this.AddToolTipInfo = null;
            this.ElementImage = null;
            this.UserDataInt32 = null;
        }
        public override string ToString()
        {
            return "Event line=" + this.LineNumber + "; Time=" + this.Time.StringBeginAndSecs + "; MergeKey=" + this.MergeKey.ToStringNull();
        }
		protected StringHeap StringHeap { get; set; }
        #endregion
        #region IEventPointer Members
        /// <summary>
        /// Klíč procesu. Nemůže být prázdný (pak se event nikam nezařadí; ledaže bychom povolili jeden empty proces - ale zatím ne).
        /// </summary>
        string IEventPointer.ProcessKey { get { return this.SecureId; } }
        /// <summary>
        /// Klíč události. Může být empty, pak ale nebude možno do této události přidávat další data o události (IEventEdge), 
        /// protože je nebude možno spárovat.
        /// </summary>
        string IEventPointer.EventKey { get { return this.BEDepth; } }
        /// <summary>
        /// Pořadové číslo události v rámci trace souboru (počátek události).
        /// Slouží k přesnému třídění pořadí událostí tehdy, když čas dvou sousedních událostí je zcela shodný.
        /// Ono za 1 milisekundu lze stihnout desítky věcí...
        /// </summary>
        int IEventPointer.LineNumber { get { return this.LineNumber; } }
        #endregion
        #region IEventItem Members
        /// <summary>
        /// Klíč skupiny událostí, které lze mezi sebou mergovat.
        /// Mergování je činnost, k níž dochází po dokončení načítání dat z trace souborů.
        /// Jejím účelem je najít formálně izolované události, které ale logicky patří do jedné události.
        /// Typickým představitelem je událost typu Cowley, která se může skládat z 5 samostatných událostí (HetCowley, RunPart1, RunPart3, RunPart4, RunPart5),
        /// ale pro uživatele má význam ji zobrazit najednou = a navíc dojde ke spojení různých fragmentů dat (číslo a název cowley, číslo třídy, název typu, metoda).
        /// Události, které se nechtějí mergovat, zde vrací null (nebo empty string).
        /// Události které mergovat chtějí, zde vrací název své skupiny, mergování pak probíhá jen pro události této skupiny.
        /// Pokračování v popisu metody IEventItem.MergeWith()
        /// </summary>
        string IEventItem.MergeKey { get { return this.MergeKey; } }
        /// <summary>
        /// Klíč domácího nodu, kam patří události tohoto procesu.
        /// Nemusí být přítomen v každé události, ale v rámci jednoho procesu nesmí být zadány dva různé údaje.
        /// </summary>
        string IEventItem.TargetHomeNode { get { return this.TargetHomeNode; } }
        /// <summary>
        /// Klíče (cesty) k cílovým nodům, kam se tato událost má zobrazit.
        /// </summary>
        string[] IEventItem.TargetPaths { get { return this.TargetPaths; } }
		/// <summary>
		/// Plné definice cest k nodům, kde se má tento element zobrazit.
		/// Pokud bude null, pak se načtou klíče z TargetPaths, a plnou definici musí určit TraceReader.
		/// </summary>
		TreePath[] IEventItem.TreePaths { get { return null; } }
		/// <summary>
        /// Čas události
        /// </summary>
        TimeRange IEventItem.Time { get { return this.Time; } }
        /// <summary>
        /// Vloží do sebe další data z položky trace souboru.
        /// Po vytvoření tohoto objektu neobsahuje vlastní data, jen klíče.
        /// Vlastní data se vkládají opakovaným voláním této metody (přinejmenším 1x).
        /// Vkládaná data mají shodnou hodnotu EventKey, jak tento objekt.
        /// Podle toho byl tento objekt vytvořen a poté při další položce dohledán.
        /// </summary>
        /// <param name="eventEdge"></param>
        void IEventItem.AddData(IEventEdge eventEdge, TraceInfo traceInfo)
        {
            TraceItem item = GetNorisASrvTraceItem(eventEdge, "Chyba: pro naplnění objektu IEventItem daty byl do metody IEventItem.AddData() předán nesprávný objekt.");
            this.AddLogin(item);
            this.AddTraceItem(item, traceInfo);

            // Zatřídění události do vizuálního stromu (TargetHomeNode, TargetPaths, ColorGroup, ColorIndex, ElementGroup):
			// Musí se provést vždy, jak po itemu Begin, tak po End.
			// Důvod: někdy uživatel prohlíží data od konce, načte trace soubor obsahující jen End, a teprve příště načte soubor obsahující Begin,
			//   a End někdy nestačí k určení cílových řádků:
            this.FillVisualTargets(item);
        }
        /// <summary>
        /// Zajišťuje doplnění Loginu do this eventu, pokud zde dosud nebyl a ve vstupních datech je uveden.
        /// </summary>
        /// <param name="item"></param>
        private void AddLogin(TraceItem item)
        {
            if (this.Login.IsEmpty() && !item.Login.IsEmpty())
                this.LoginInt32 = item.LoginInt32;
        }
        /// <summary>
        /// Zkusí, zda tato událost (this) může být spojena s událostí časově následující ze stejné mergovací skupiny.
        /// Pokud ano, pak se v této metodě musí prolnout data obou událostí do this, a musí se vrátit true.
        /// Pokud je mergovat nelze, vrací se false.
        /// </summary>
        /// <param name="nextEventItem">Časově následující událost</param>
        /// <returns></returns>
        bool IEventItem.MergeWith(IEventItem nextEventItem)
        {
            if (!(nextEventItem is EventItem)) return false;
            EventItem thing = nextEventItem as EventItem;
            if (thing == null) return false;
            return this.MergeWith(thing);
        }
        /// <summary>
        /// Varianta barevnosti, používá se společně s this.ColorGroup.
        /// Elementy, které mají shodnou property ColorGroup i ColorIndex budou mít shodnou barvu.
        /// Hodnota ColorIndex dovoluje variovat základní barvu definovanou pro skupinu ColorGroup.
        /// Implicitní hodnota je přebírána z this.LineNumber, po vložení jiné hodnoty je vracena tato explicitní hodnota.
        /// Je vhodné vkládat například číslo funkce, číslo třídy instance atd, pak všechny výskyty této funkce mají stejnou barvu.
        /// Zohlední se pouze tehdy, když je vygenerován element s barvou Empty.
        /// </summary>
        int IEventItem.ColorIndex { get { return this.ColorIndex; } }
        #endregion
		#region Datové a vizuální property, základní metody
		/// <summary>
		/// Reference na objekt Trace, který událost založil, nebo u párových událostí na Trace, který je Begin
		/// </summary>
		public TraceInfo Trace { get; protected set; }
        /// <summary>
        /// Klíč session = identifikace procesu (ProcessKey)
        /// </summary>
		public string SecureId { get { return this.StringHeap.GetString(this.SecureIdInt32); } }
		public Int32 SecureIdInt32 { get; protected set; }
        /// <summary>
        /// Klíč BEDepth = EventKey = identifikace události (jednotlivé řádky trace souboru se stejnou hodnotou se budou přidávat do jedné události, typicky Begin + End).
        /// </summary>
		public string BEDepth { get { return this.StringHeap.GetString(this.BEDepthInt32); } }
		public Int32 BEDepthInt32 { get; protected set; }
        /// <summary>
        /// Fyzické číslo řádku, který událost založil (pochází z první řádky ITraceItem.LineNumber)
        /// </summary>
        public int LineNumber { get; protected set; }
		/// <summary>
		/// Čas / časový interval této události.
        /// Do této property se nevepisují změny při mergování, tento čas je čistým časem konkrétní události.
        /// Mergovaný čas lze najít v property MergedTime.
		/// </summary>
        public TimeRange Time { get; protected set; }
        /// <summary>
        /// Rozšířená informace o typu události (Event / Info / Warning / Error / Deadlock / ...)
        /// </summary>
        public StatusInfoType StatusInfo { get; protected set; }
        /// <summary>
		/// Obrázek, který se kreslí u elementu
		/// </summary>
		public Image ElementImage { get; protected set; }
        /// <summary>
        /// Vlastnosti elementu.
        /// Defaultní vlastnosti jsou Selectable.
        /// </summary>
        internal TraceVisualiser.Components.ElementProperties ElementProperties { get; set; }
		/// <summary>
        /// Umístění obrázku (this.ElementImage) vzhledem k prostoru elementu:
        /// Zde se určuje umístění pivot bodu elementu (který je totožný s pivot pointem obrázku = ImagePivotImagePoint).
        /// Lze tak umístit například střed obrázku (ImagePivotImagePoint = ContentAlignment.MiddleCenter) na střed horní hrany elementu (ImagePivotElementPoint = ContentAlignment.TopCenter).
        /// </summary>
		internal ContentAlignment ImagePivotElementPoint { get; set; }
        /// <summary>
        /// Umístění obrázku (this.ElementImage) vzhledem k prostoru elementu:
        /// Tento bod obrázku bude totožný s pivot pointem elementu ImagePivotElementPoint.
        /// Lze tak umístit například střed obrázku (ImagePivotImagePoint = ContentAlignment.MiddleCenter) na střed horní hrany elementu (ImagePivotElementPoint = ContentAlignment.TopCenter).
        /// </summary>
		internal ContentAlignment ImagePivotImagePoint { get; set; }
        /// <summary>
        /// Login uživatele = určuje HomeNode. Plní se z první věty, a v každém dalším plnění (AddData) se prověřuje, zda je null a na vstupu je zadaný = pak se doplní.
        /// </summary>
		public string Login { get { return this.StringHeap.GetString(this.LoginInt32); } }
		public Int32 LoginInt32 { get; protected set; }
        /// <summary>
        /// Základní informace ze vstupního souboru (Info / Warning / Error)
        /// </summary>
        public LineIWE Level { get; protected set; }
        /// <summary>
		/// Obsah zásobníku
		/// </summary>
		public string Stack { get { return this.StringHeap.GetString(this.StackInt32); } }
		public Int32 StackInt32 { get; protected set; }
		/// <summary>
		/// ThreadID
		/// </summary>
        public int ThreadId { get; protected set; }
		/// <summary>
		/// RoundtripID
		/// </summary>
        public int RoundtripId { get; protected set; }
		/// <summary>
		/// PageID
		/// </summary>
        public int PageId { get; protected set; }
		/// <summary>
		/// Type
		/// </summary>
		public string Type { get { return this.StringHeap.GetString(this.TypeInt32); } }
		public Int32 TypeInt32 { get; protected set; }
		/// <summary>
		/// Method
		/// </summary>
		public string Method { get { return this.StringHeap.GetString(this.MethodInt32); } }
		public Int32 MethodInt32 { get; protected set; }
		/// <summary>
		/// Keyword
		/// </summary>
		public string Keyword { get { return this.StringHeap.GetString(this.KeywordInt32); } }
		public Int32 KeywordInt32 { get; protected set; }
		/// <summary>
		/// Počet položek UserData.
		/// </summary>
		public int UserLength { get; protected set; }
		/// <summary>
		/// Informace uložené v řádku v objektech UserXxx, pokud je potomek této třídy nepřevezme a neuloží jinak.
		/// Ukládání do těchto property provádí metoda this.UserDataStore(), jejíž volání není povinné.
		/// </summary>
		public string UserData(int index) { return this.StringHeap.GetString(this.UserDataInt32[index]); }
		public Int32[] UserDataInt32 { get; protected set; }

        // Property pro zatřídění události do vizuálního stromu:
        /// <summary>
        /// Domácí node této události = řádek s uživatelem (user\login).
        /// Pokud v tomto řádku není vyplněn login, bude zde null.
        /// </summary>
		public string TargetHomeNode { get { return this.StringHeap.GetString(this.TargetHomeNodeInt32); } set { this.TargetHomeNodeInt32 = this.StringHeap.GetInt32(value); } }
		public Int32 TargetHomeNodeInt32 { get; protected set; }
        /// <summary>
        /// Seznam nodů, do nichž se má tato událost promítat. Relativní nody nezačínají zpětným lomítkem, a vztahují se k this.TargetHomeNode
        /// U většiny (asi všechny) událostí to bude node relativní k uživateli ("20:Instance").
        /// U výjimečných (Thread.RunProcess) to bude i přímo node uživatele ("").
        /// U SQL příkazů to bude i node SQL příkazů ("\AplSrv\20:SQL příkazy").
        /// U stavu paměti to bude i node zobrazující graf paměti ("\AplSrv\10:Využití paměti").
        /// </summary>
		public string[] TargetPaths { get { return this.StringHeap.GetStrings(this.TargetPathsInt32); } set { this.TargetPathsInt32 = this.StringHeap.GetPointers(value); } }
		public Int32[] TargetPathsInt32 { get; protected set; }
        /// <summary>
        /// Skupina barevnosti, umožní konfigurovat barvy pro jednotlivé skupiny událostí bez toho, aby tuto konfiguraci řešila datová vrstva.
        /// Ta jen nadefinuje logickou skupinu barvy, ale hodnotu určuje konfigurace.
        /// V konfiguraci může být uvedeno více barev pro jednu ColorGroup, pak se varianta konkrétní barvy určuje podle indexu ColorIndex.
        /// Zohlední se pouze tehdy, když je vygenerován element s barvou Empty.
        /// </summary>
		public virtual string ColorGroup { get { return this.StringHeap.GetString(this.ColorGroupInt32); } set { this.ColorGroupInt32 = this.StringHeap.GetInt32(value); } }
		public Int32 ColorGroupInt32 { get; protected set; }
        /// <summary>
        /// Varianta barevnosti, používá se společně s this.ColorGroup.
        /// Elementy, které mají shodnou property ColorGroup i ColorIndex budou mít shodnou barvu.
        /// Hodnota ColorIndex dovoluje variovat základní barvu definovanou pro skupinu ColorGroup.
        /// Implicitní hodnota je přebírána z this.LineNumber, po vložení jiné hodnoty je vracena tato explicitní hodnota.
        /// Je vhodné vkládat například číslo funkce, číslo třídy instance atd, pak všechny výskyty této funkce mají stejnou barvu.
        /// Zohlední se pouze tehdy, když je vygenerován element s barvou Empty.
        /// </summary>
        public int ColorIndex { get { return (this._ColorIndex.HasValue ? this._ColorIndex.Value : this.LineNumber); } protected set { this._ColorIndex = value; } }
        protected int? _ColorIndex;

		/// <summary>
		/// Uložení UserData z LineInfo do this.
		/// Tuto metodu nechť volá takový potomek, který si neví rady s UserData (nemá pro ně specifické property), 
		/// ale chce je uschovat v objektu (protože LineInfo se po zpracování zahazuje).
		/// </summary>
		/// <param name="line"></param>
		protected virtual void UserDataStore(TraceItem item)
		{
			this.UserLength = item.UserLength;
			if (this.UserLength > 0)
			{
				this.UserDataInt32 = new Int32[this.UserLength];
				Array.Copy(item.UserDataInt32, 0, this.UserDataInt32, 0, this.UserLength);
			}
		}
        /// <summary>
        /// Převede (pouze přetypuje) parametr z obecného typu ITraceItem traceItem na zdejší bázový typ NorisASrvTraceItem.
        /// Pokud nejde převést, vyhodí chybu.
        /// </summary>
        /// <param name="traceItem"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        protected TraceItem GetNorisASrvTraceItem(IEventEdge eventEdge, string msg)
        {
            if (eventEdge == null)
                Msg.Error(msg + "\r\nPředaný objekt je null.");

            TraceItem item = eventEdge as TraceItem;
            if (item == null)
                Msg.Error(msg + "\r\nOčekávaný typ: {0}, chybný typ: {1}", typeof(TraceItem).Name, eventEdge.GetType().Name);

            return item;
        }
		#endregion
		#region Vkládání dat z Items do this: abstract metoda, support pro vkládání standardních dat
        protected abstract void AddTraceItem(TraceItem item, TraceInfo traceInfo);
        /// <summary>
        /// Do nově vzniklého objektu vloží základní data z NorisASrvTraceItem. Jde o data typu IEventPointer.
        /// Nevkládá aplikační data, to si řeší konkrétní potomek, a je to voláno z metody ...
        /// Tato metoda se volá ze všech statických konstruktorů mých potomků.
        /// Zdejší metoda ukládá pouze ta data, která jsou shodná ve větě Begin i End, protože se nikdy neví, zda první načtená položka (TraceItem item) bude Begin nebo End,
        /// protože každý záznam může být fyzicky v jiném souboru, a uživatel může prohlížet data od začátku i od konce.
        /// Zde se neurčuje zatřídění události do vizuálních řádků (tj. odsud se nevolá metoda this.FillVisualTargets()), zdejší metoda se volá z konstruktoru.
        /// Určení vizuální příslušnosti události se volá typicky až na konci metody this.AddData(), anebo si může metodu this.FillVisualTargets(item) zavolat potomek kdykoliv.
        /// Nebo ji může přepsat...
        /// </summary>
        /// <param name="item"></param>
        protected virtual void FillFromItem(TraceItem item)
        {
            // Tato data jsou jak v v item Begin tak End shodná:
            this.SecureIdInt32 = item.SecureIdInt32;         // ProcessKey
			this.BEDepthInt32 = item.BEDepthInt32;           // EventKey
			this.MergeKeyInt32 = item.MergeKeyInt32;
        }
        /// <summary>
        /// Uloží informace ze vstupní položky, pro položku System nebo Begin
        /// </summary>
        /// <param name="item"></param>
        /// <param name="traceInfo"></param>
		/// <param name="onlyEmpty">Plnit pouze do položek, které jsou Empty (typicky při plnění daty z itemu End, tehdy když nechcem přepsat případná data načtená z Begin)</param>
        protected void StoreItemBegin(TraceItem item, TraceInfo traceInfo, bool onlyEmpty)
        {
			bool atBegin = !onlyEmpty;               // true = plnit vše (jsme na begin nebo System)

            // Identifikace:
			if (atBegin)
			{
				this.LineNumber = item.LineNumber;
				this.Trace = traceInfo;
			}
            // Vnitřní data:

			if (atBegin || this.Level == LineIWE.None)
	            this.Level = item.Level;
			if (atBegin || this.StackInt32 == 0)
				this.StackInt32 = item.StackInt32;
			if (atBegin || this.ThreadId == 0)
				this.ThreadId = item.ThreadId;
			if (atBegin || this.RoundtripId == 0)
				this.RoundtripId = item.RoundtripId;
			if (atBegin || this.PageId == 0)
				this.PageId = item.PageId;
			if (atBegin || this.TypeInt32 == 0)
				this.TypeInt32 = item.TypeInt32;
			if (atBegin || this.MethodInt32 == 0)
				this.MethodInt32 = item.MethodInt32;
			if (atBegin || this.KeywordInt32 == 0)
				this.KeywordInt32 = item.KeywordInt32;
			if (atBegin || this.LoginInt32 == 0)
				this.LoginInt32 = item.LoginInt32;
        }
		#endregion
        #region Zatřídění události do vizuálního stromu (TargetHomeNode, TargetPaths, ColorGroup, ColorIndex, ElementGroup)
        /// <summary>
        /// Zatřídí tuto událost (this) do vizuálního systému.
        /// Tato metoda se běžně vyvolává na konci metody AddData, tedy poté, kdy jsou do this vložena všechna data vstupní položky Item.
        /// Běžně (z bázové třídy) se tato metoda volá tehdy, když platí if (this.TargetPaths == null).
        /// </summary>
        /// <param name="item"></param>
        protected virtual void FillVisualTargets(TraceItem item)
        {
            this.TargetHomeNode = null;
            List<string> paths = new List<string>();
            string colorGroup = "";
            int elementGroup = 0;

            string sqlPid;
            // Strojová klíčová slova (this.Keyword), kterých je hodně (30?)
            //  převedu do významových skupin, které se zobrazí jako řádky grafu pod jedním uživatelem.
            //  Významová skupina se ukládá do this.SubNode(Name + SortKey).
            // V rámci řádku lze elementy ještě třídit do podskupin 
            //  = elementy na sobě leží ne jako naházená sláma, ale jednotlivé podskupiny se drží ve své "linii".
            //  Skupina se ukládá do this.ElementGroup.
            // Barevnost elementu se určuje až v IThing:
            //  - a to buď explicitně (IThing.BackColor)
            //       => tím ale vyloučíme následující možnost uživatele konfigurovat barvy (Color.Green už nelze zobrazit jinak),
            //  - anebo určíme skupinu barvy (IThing.ColorGroup)
            //       => a tím uživatel dostane možnost si pro tuto skupinu nadefinovat svoji vlastní barvu (nebo variantní sadu barev).
            // Tady jsem se rozhodl jít druhou cestou, IThing.ColorGroup, a zde už připravím tuto hodnotu (zde ji lze snadno detekovat).
            //  - Zde určená ColorGroup (uložená v typu NorisASrvTraceItem) přejde do IThing takto:
            //     metoda NorisASrvTraceReader._CreateIThing() => NorisASrvThing.CreateThing() => NorisASrvThing.StoreCommonData().

            // Text metody mírně upravím:
			string method = this.Method;
			method = ((method == null) ? "" : method.ToLower());
            method = method.Replace("_", "");
            method = method.Replace("(1)", "");

            // Podle klíčových dat
            string kwdUppered = this.Keyword;
			kwdUppered = ((kwdUppered == null) ? "" : kwdUppered.ToUpper());
			switch (kwdUppered)
            {
                // --- Keyword -------------- Method -------
                //MAF.APOLLO+
                case "SYSTEMERROR":
                case "USERERROR":
                case "SYSTEMUSERERROR":
                    paths.Add(ApolloAplConstants.SubNodeUserKernel);
                    colorGroup = ApolloAplConstants.ColorGroupKernelDot + kwdUppered + "." + this.Method;
                    elementGroup = 15;
                    break;
                //MAF.APOLLO-
                case "ATTEMPT":
                    paths.Add(ApolloAplConstants.SubNodeUserKernel);
                    colorGroup = ApolloAplConstants.ColorGroupKernelDot + "Attempt";
                    elementGroup = 10;
                    break;

                case "AFTER":             // ItemChanged
                case "BEFORE":
                case "ITEMCHANGEDBEFORE":
                    paths.Add(ApolloAplConstants.SubNodeUserInstance);
                    colorGroup = ApolloAplConstants.ColorGroupInstanceDot + this.Method + "." + kwdUppered;    // Tady je to opačně! Běžně je Keyword.Method, ale tady Method.Keyword 
                    elementGroup = 20;
                    break;

                case "AUDITOR":           // Begin, End
                    paths.Add(ApolloAplConstants.SubNodeUserKernel);
                    colorGroup = ApolloAplConstants.ColorGroupKernelDot + kwdUppered + "." + this.Method; ;
                    elementGroup = 10;
                    break;

                case "CACHE":
                    paths.Add(ApolloAplConstants.SubNodeUserKernel);
                    colorGroup = ApolloAplConstants.ColorGroupKernelDot + kwdUppered;
                    elementGroup = 20;
                    break;

                case "CLIENTERROR":

                case "COWLEY":            // _GetCowley, _Run1_nonvisualPart, _Run2_progressbarPart, _Run3_rundonePart, _Run5_invokePart
                    paths.Add(ApolloAplConstants.SubNodeUserCowley);
					colorGroup = ApolloAplConstants.ColorGroupCowleyDot + method;
                    elementGroup = 10;
                    break;

                case "DATA":
                case "DATACONTAINER":     // _Retrieve, Invalidate, SetInvalidate, 
                case "DATAFORM":          // 
                case "DATASTORE":
                case "ERROR":
                case "EXTENSIONS":
                case "FORMAT":
                case "FORMDETAIL":
                case "GETINFO":
                case "GETTEMPLATE":
                    paths.Add(ApolloAplConstants.SubNodeUserKernel);
                    colorGroup = ApolloAplConstants.ColorGroupKernelDot + kwdUppered + "." + this.Method;
                    elementGroup = 15;
                    break;

                case "INSTANCE":          // _InitDataStores, _InitDataStoresGetSelects, GetInstance, Init, NewEntry, RecountEntries, Reset, Retrieve, SetFolder, Update
                    paths.Add(ApolloAplConstants.SubNodeUserInstance);
                    colorGroup = ApolloAplConstants.ColorGroupInstanceDot + method;
                    elementGroup = 10;
                    break;

                case "LOGIN":
                    paths.Add(ApolloAplConstants.SubNodeUserKernel);
                    colorGroup = ApolloAplConstants.ColorGroupKernelDot + "Cache";
                    elementGroup = 15;
                    break;
                case "MENUACTION":
                case "ONDBLCLICK":
                case "ONROWCHANGED":
                    paths.Add(ApolloAplConstants.SubNodeUserInstance);
                    colorGroup = ApolloAplConstants.ColorGroupInstanceDot + kwdUppered;
                    elementGroup = 30;
                    break;

                case "OPENWINDOW":

                case "PERIODS":
                case "PROGRESS":
                    paths.Add(ApolloAplConstants.SubNodeUserKernel);
                    colorGroup = ApolloAplConstants.ColorGroupKernelDot + "Progress";
                    elementGroup = 30;
                    break;

                case "RECORDRULES":       // FlowDefaults, Reset, TestSave, TestAttr, 
                    paths.Add(ApolloAplConstants.SubNodeUserInstance);
                    colorGroup = ApolloAplConstants.ColorGroupInstanceDot + kwdUppered + "." + this.Method;
                    elementGroup = 50;
                    break;

                case "RELATIONS":         // SetFolder, 
                    paths.Add(ApolloAplConstants.SubNodeUserInstance);
                    colorGroup = ApolloAplConstants.ColorGroupInstanceDot + kwdUppered + "." + this.Method;
                    elementGroup = 20;
                    break;

                case "ROOT":
                case "SHOWERROR":
                    paths.Add(ApolloAplConstants.SubNodeUserKernel);
                    colorGroup = ApolloAplConstants.ColorGroupKernelDot + kwdUppered;
                    elementGroup = 30;
                    break;

                case "SQL":               // _CreateSummaryTable, _DoUpdate, _ExecuteNonQuery, _GetDynRelCountForAllFolders, _getvalue, _LoadMore, ExecuteScalar, Fill(1), GetDbDataReader
                    paths.Add(ApolloAplConstants.SubNodeUserSql);

                    // SQL přidávám ještě do řádku Aplikační server/SQL příkazy, a protože jich očekávám více, tak je člením do řádků podle jejich PID:
                    sqlPid = _GetItemUserValueSqlPid(item, 0);
                    paths.Add(ApolloAplConstants.RootNodeAplSql + "\\" + sqlPid);

                    // Detekuji klíčové slovo SQL příkazu, podle něj určím ColorGroup:
                    string sqlKey = this.GetColorGroupKeyword();
                    colorGroup = kwdUppered + sqlKey;
                    elementGroup = 20;
                    break;

                case "STORAGE":
                case "STORES":
                case "SWT":
                case "USERSCOUNT":
                case "TEMPLATE":
                    paths.Add(ApolloAplConstants.SubNodeUserKernel);
                    colorGroup = ApolloAplConstants.ColorGroupKernelDot + kwdUppered + "." + method;
                    elementGroup = 20;
                    break;

                case "THREAD":            // ProcessRequest, RunThread, Suspend, ResumeThread
                    paths.Add(ApolloAplConstants.SubNodeUserKernel);
                    // Které události zobrazím v řádku s uživatelem (hlavičkový řádek, události jsou vidět i bez otevření nodu uživatele):
                    bool showAction = false;
                    bool showInUserLine = (kwdUppered == "THREAD" && (this.Method == "RunThread" || this.Method == "Suspend" || this.Method == "ResumeThread")); // původně: this.Method == "ProcessRequest");
                    if (this.UserLength >= 2)
                    {   // Některé akce se zobrazovat nebudou:
                        showAction = true;
                        string action = this.UserData(1);
                        if (action == "PRGSGetSettings" || action == "OnRowChanged" || action == "ItemChanged" || action == "RowFocusChanged" || action == "ClosingWindow" || action == "KeepMySession" ||
                            action == "RefreshControls" || action == "TabPageChanged" || action == "RefreshRow" || action == "RowFilter")
                            showAction = false;
                    }
                    if (showInUserLine && showAction)
                        paths.Add(ApolloAplConstants.SubNodeUser);
                    colorGroup = ApolloAplConstants.ColorGroupKernelDot + kwdUppered + "." + method;
                    elementGroup = 10;
                    break;

                case "TRANS":             // _CheckState, Begin, End(Begin), End(End)
                    paths.Add(ApolloAplConstants.SubNodeUserSql);

                    // SQL přidávám ještě do řádku Aplikační server/SQL příkazy, a protože jich očekávám více, tak je člením do řádků podle jejich PID:
                    sqlPid = _GetItemUserValueSqlPid(item, 0);
                    paths.Add(ApolloAplConstants.RootNodeAplSql + "\\" + sqlPid);

                    colorGroup = kwdUppered + "." + this.Method;
                    elementGroup = 10;
                    break;

                case "TUNNEL":
                    paths.Add(ApolloAplConstants.SubNodeUserKernel);
                    colorGroup = ApolloAplConstants.ColorGroupKernelDot + kwdUppered + "." + method;
                    elementGroup = 50;
                    break;

                case "WARNING":
                    paths.Add(ApolloAplConstants.SubNodeUserKernel);
                    colorGroup = ApolloAplConstants.ColorGroupKernelDot + "Warning";
                    elementGroup = 50;
                    break;

                default:
                    paths.Add(ApolloAplConstants.SubNodeUserKernel);
                    colorGroup = ApolloAplConstants.ColorGroupKernelDot + kwdUppered;
                    elementGroup = 50;
                    break;

            }

            // Zajistím změnu pro události ve stavu Info, Warning, Error, a další errory. Nikoliv stavy Event.
            // Ponechám jejich rozčlenění do ElementGroup a ColorGroup, ale dám je do jiného řádku:
            if (this.StatusInfo != StatusInfoType.Event)
            {   // Pro událost typu Info vyměním cílový node z user/něco na user/kernel/system:
                // Tento node je určen v 1. položce listu paths, a nezačíná absolutní cestou:
                bool path0IsUser = (paths.Count > 0 && !paths[0].StartsWith("\\"));
                
                // Tuto událost navíc zobrazím ve společném řádku AplServer/SysInfo/(Info/Warning/Error):
                //  poznámka: Errory rovnou do řádku AplSystem, protože nebudu muset rozbalovat AplSystem (tím bych rozbalil i SysInfo, a těch je řada milionů):
                switch (this.StatusInfo)
                {
                    case StatusInfoType.Information:
                        if (path0IsUser)
                            paths[0] = ApolloAplConstants.SubNodeUserKernelInfo;
                        paths.Add(ApolloAplConstants.RootNodeAplSystemInfo);
                        break;
                    case StatusInfoType.Warning:
                        if (path0IsUser)
                            paths[0] = ApolloAplConstants.SubNodeUserKernelInfo;
                        paths.Add(ApolloAplConstants.RootNodeAplSystemWarning);
                        break;
                    case StatusInfoType.Error:
                        paths.Add(ApolloAplConstants.RootNodeAplSystem);
                        paths.Add("");                // Errory budu zobrazovat i v rootu uživatele
                        break;
                    case StatusInfoType.Deadlock:
                        paths.Add(ApolloAplConstants.RootNodeAplSql);
                        paths.Add("");                // Errory budu zobrazovat i v rootu uživatele
                        break;
                }
            }

            // HomeNode pro tuto událost je "Uživatelé/(login)", ale jen pokud je známý login uživatele:
			string login = this.Login;
			this.TargetHomeNode = (String.IsNullOrEmpty(login) ? null : ApolloAplConstants.RootNodeUser + "\\" + login);   // Převede se na Int32 a uloží
			this.TargetPaths = paths.ToArray();           // Kde se bude událost zobrazovat = souhrn cest.      // Převede se na Int32 a uloží
			this.ColorGroup = colorGroup;                 // Převede se na Int32 a uloží
			this.ElementGroup = elementGroup;             // Převede se na Int32 a uloží
        }
        /// <summary>
        /// Vrátí SQL PID z itemu, z daného user sloupce (index, počínaje 0).
        /// </summary>
        /// <param name="item"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        private string _GetItemUserValueSqlPid(TraceItem item, int index)
        {
            string sqlPid = (item.UserLength > index ? item.User(index) : "");
            if (sqlPid.IsEmpty()) sqlPid = "0";
            return sqlPid.Trim().PadLeft(4, '0');
        }
		#endregion
        #region Podpora mergování elementů (izolované elementy, které patří logicky do jednoho elementu, například: NrsCowley)
        /// <summary>
        /// Klíč skupiny událostí, které lze mezi sebou mergovat.
        /// Mergování je činnost, k níž dochází po dokončení načítání dat z trace souborů.
        /// Jejím účelem je najít formálně izolované události, které ale logicky patří do jedné události.
        /// Typickým představitelem je událost typu Cowley, která se může skládat z 5 samostatných událostí (HetCowley, RunPart1, RunPart3, RunPart4, RunPart5),
        /// ale pro uživatele má význam ji zobrazit najednou = a navíc dojde ke spojení různých fragmentů dat (číslo a název cowley, číslo třídy, název typu, metoda).
        /// Události, které se nechtějí mergovat, zde vrací null (nebo empty string).
        /// Události které mergovat chtějí, zde vrací název své skupiny, mergování pak probíhá jen pro události této skupiny.
        /// Pokračování v popisu metody IEventItem.MergeWith()
        /// </summary>
		public string MergeKey { get { return this.StringHeap.GetString(this.MergeKeyInt32); } set { this.MergeKeyInt32 = this.StringHeap.GetInt32(value); } }
		public Int32 MergeKeyInt32;
        /// <summary>
        /// Dotaz, zda lze položku nextEvent připojit do položky this (pak by položka this byla výsledkem, do něhož se přidá nextEvent).
        /// Bázová metoda je virtuální, vrací false.
        /// Pokud se potomek ve své override metodě rozhodne sloučit tyto dvě události, pak to rovnou provede!!!
        /// Žádná další metoda se volat nebude.
        /// Výstup: true = objekty byly sloučeny / false = tyto objekty nelze sloučit, objekt (this, ten dřívější) se slučovat nebude.
        /// </summary>
        /// <param name="nextEvent">Následující položka</param>
        /// <returns></returns>
        public virtual bool MergeWith(EventItem nextEvent)
        {
            return false;
        }
        /// <summary>
        /// Čas vizuálního elementu = čas zdejší, anebo pokud je mergováno, tak čas mergovaný
        /// </summary>
        public virtual TimeRange ElementTime
        {
            get { return (this.ContainAnyMergedEvents ? this.MergedTime : this.Time); }
        }
        /// <summary>
        /// Nejistota času počátku vizuálního elementu = buď zdejší, anebo pokud je mergováno, tak mergovaný údaj
        /// </summary>
        public virtual bool ElementBeginIsUncertain
        {
            get { return (this.ContainAnyMergedEvents ? this.MergedBeginIsUncertain : this.BeginIsUncertain); }
        }
        /// <summary>
        /// Nejistota času konce vizuálního elementu = buď zdejší, anebo pokud je mergováno, tak mergovaný údaj
        /// </summary>
        public virtual bool ElementEndIsUncertain
        {
            get { return (this.ContainAnyMergedEvents ? this.MergedEndIsUncertain: this.EndIsUncertain); }
        }
        /// <summary>
        /// Zajistí propojení eventu nextEvent do eventu this.
        /// </summary>
        /// <param name="nextEvent"></param>
        protected virtual void MergeEvent(EventItem nextEvent)
        {
            if (this.MergedItems == null)
                this.MergedItems = new List<EventItem>();
            this.MergedItems.AddRange(nextEvent.PullOutMergedEvents());

            // Provedu rekalkulaci (čas, Uncertains):
            TimeRange mergedTime = this.Time;
            bool beginUncert = this.BeginIsUncertain;
            bool endUncert = this.EndIsUncertain;
            foreach (EventItem merged in this.MergedItems)
            {
                // Nejistota na konci / na začátku: kterýkoliv jeden jediný nejistý event způsobí nejistotu celku:
                beginUncert |= merged.BeginIsUncertain;
                endUncert |= merged.EndIsUncertain;
                mergedTime = mergedTime + merged.Time;
            }

            this.MergedTime = mergedTime;
            this.MergedBeginIsUncertain = beginUncert;
            this.MergedEndIsUncertain = endUncert;
        }
        /// <summary>
        /// Vrátí sebe a svůj seznam mergovaných elementů, rekurzivně. Svůj seznam nulluje.
        /// </summary>
        /// <returns></returns>
        protected IEnumerable<EventItem> PullOutMergedEvents()
        {
            this.MergedOut = true;
 	        List<EventItem> result = new List<EventItem>();
            result.Add(this);                                     // Dám tam sebe
            if (this.ContainAnyMergedEvents)
            {
                foreach (EventItem item in this.MergedItems)      // A za každý event, který je do mě přimergovaný
                    result.AddRange(item.PullOutMergedEvents());  //  tam dám jeho, (a i jeho přimergované eventy).
                this.MergedItems = null;                          // Svůj seznam nulluju (moji podřízení si jej nullovali před chvilkou)
            }
            return result;
        }
        /// <summary>
        /// Příznak, že this v sobě zahrnuje některé mergované události
        /// </summary>
        protected bool ContainAnyMergedEvents { get { return (this.MergedItems != null); } }
        /// <summary>
        /// Zjistí, zda this event v sobě obsahuje konkrétní mergovanou událost.
        /// Používá se při opakovaném mergování (ReMerge) události, ve které došlo při donačítání dat ke změně (do události se vložila další data, a událost se znovu zařadila do mergování).
        /// Takovou událost je nutno při mergování do this vždy povolit a přidat, bez ohledu na jiné podmínky.
        /// </summary>
        /// <param name="nextCowley"></param>
        /// <returns></returns>
        protected bool ContainMergedEvent(EventCowleyItem nextCowley)
        {
            return (this.ContainAnyMergedEvents && this.MergedItems.Exists(ev => Object.ReferenceEquals(ev, nextCowley)));
        }
        /// <summary>
        /// Souhrn všech mergovaných elementů. Typicky je null (jde o samostatný event).
        /// Při mergování se sem vkládají reference na přimergované elementy, 
        /// a rekurzivně na jejich mergované elementy, a potom se jejich MergedItems vyprazďnuje.
        /// </summary>
        protected List<EventItem> MergedItems { get; set; }
        /// <summary>
        /// Čas vzniklý sloučením času this a všech mergovaných elementů.
        /// Pokud se nic nemerguje, je MergedTime == Empty a čas pro zobrazení události se bere z this.Time = nativní čas samotné události.
        /// </summary>
        protected TimeRange MergedTime { get; set; }
        /// <summary>
        /// Příznak, že tento event byl mergován někam jinam. Snad nepotřebuji vědět kam, hlavně že vím, že this je mergován ven.
        /// </summary>
        public bool MergedOut { get { return BooleanGet(7); } protected set { BooleanSet(7, value); } }
        #endregion
        #region Podpora pro tvorbu grafického elementu (Captions, ToolTip, ...)
        /// <summary>
        /// Výška elementu v poměru k výšce řádku, uplatňuje se pouze u grafu typu BarGraph
        /// </summary>
        public float ElementHeightRatio { get; set; }
        /// <summary>
        /// Příznak, že čas bude vždy jen jednorozměrný = v čase Time.Begin (čas End by měl být roven Begin).
        /// Slouží pro korekci času this.Time při vytváření grafického elementu.
        /// </summary>
        public bool OneDimensional { get { return BooleanGet(0); } protected set { BooleanSet(0, value); } }
        /// <summary>
        /// Příznak, že pokud má element čas s nulovou délkou (kterou by systém dokázal vykreslit), je žádoucí kreslení rectangle potlačit, bude se vykreslovat pouze Image.
        /// </summary>
        public bool HideZeroWidthElementRectangle { get { return BooleanGet(1); } protected set { BooleanSet(1, value); } }
        /// <summary>
        /// true: Začátek intervalu je nejistý (čas počátku odhadujeme podle počátku nadřízeného objektu, ale jistotu nemáme)
        /// </summary>
        public virtual bool BeginIsUncertain { get { return BooleanGet(2); } protected set { BooleanSet(2, value); } }
        /// <summary>
        /// true: Konec intervalu je nejistý (čas počátku odhadujeme podle počátku nadřízeného objektu, ale jistotu nemáme)
        /// </summary>
        public virtual bool EndIsUncertain { get { return BooleanGet(3); } protected set { BooleanSet(3, value); } }
        /// <summary>
        /// true: Mergovaný Začátek intervalu je nejistý (čas počátku odhadujeme podle počátku nadřízeného objektu, ale jistotu nemáme)
        /// </summary>
        public bool MergedBeginIsUncertain { get { return BooleanGet(4); } protected set { BooleanSet(4, value); } }
        /// <summary>
        /// true: Mergovaný Konec intervalu je nejistý (čas počátku odhadujeme podle počátku nadřízeného objektu, ale jistotu nemáme)
        /// </summary>
        public bool MergedEndIsUncertain { get { return BooleanGet(5); } protected set { BooleanSet(5, value); } }
        /// <summary>
        /// Skupina událostí, ve které se vykresluje element s touto událostí.
        /// Elementy odlišných skupin se při vykreslení navzájem nemíchají v jedné lince řádku.
        /// </summary>
        public virtual int ElementGroup { get; protected set; }
        /// <summary>
        /// Barva pozadí elementu.
        /// Výchozí je barva Empty.
        /// Pro element s barvou Empty si barvu určuje nadřízená vrstva tak, že si přečte property ColorGroup, 
        /// a pro tuto skupinu barev vyhledá konkrétní barvu v konfiguraci UI.
        /// K tomu je třeba zmínit property ColorIndex, která umožní barvu variovat (aby nebyly všechny elementy téže skupiny jednobarevné), více viz tam.
        /// </summary>
        public Color DetailBackColor { get; protected set; }
        /// <summary>
        /// Tento element je viditelný (pokud má kladný čas)
        /// </summary>
        protected virtual bool ElementVisible { get { return BooleanGet(6); } set { BooleanSet(6, value); } }
        /// <summary>
        /// Zde potomek musí vytvořit souhrn textů pro zobrazení uvnitř elementu
        /// </summary>
        /// <returns></returns>
        protected abstract string[] CreateCaptions();
        /// <summary>
        /// Zde potomek musí vytvořit souhrn textů pro zobrazení uvnitř elementu
        /// </summary>
        /// <returns></returns>
        protected abstract string CreateToolTip();
        /// <summary>
        /// Vrátí text Caption s největší délkou + přidá 2x CrLf.
        /// Pokud neexistuje žádný text Caption, vrátí prázdný string.
        /// </summary>
        /// <returns></returns>
        protected virtual string CaptionMaxima
        {
            get
            {
                string maxima = null;
                string[] captions = CreateCaptions();
                foreach (string caption in captions)
                {
                    if (!String.IsNullOrEmpty(caption) && (maxima == null || caption.Length > maxima.Length))
                        maxima = caption;
                }
                if (maxima == null) return "";
                return maxima + Environment.NewLine + Environment.NewLine;
            }
        }
        /// <summary>
        /// Metoda má za úkol vrátit klíčové slovo sebe sama, které bude hrát roli v ColorGroup. Slovo má mít na začátku tečku!!!
        /// Metoda se volá jen pro některá klíčová slova.
        /// </summary>
        /// <returns></returns>
        protected abstract string GetColorGroupKeyword();
		/// <summary>
		/// Vrátí SubTimeSort = číslo trace v čase + číslo řádky
		/// </summary>
		/// <returns></returns>
		private int _GetSubTimeSort()
		{
			// Vrací Int32, který obsahuje bit 31 = 0 (kladné číslo), bity 30 ÷ 25 (6 bitů) = číslo TraceOrder, bity 24 ÷ 0 = číslo LineNumber:
			return (this.Trace == null ? 0 : (this.Trace.TraceOrder & 0x003F) << 25) | (this.LineNumber & 0x00FFFFFF);
		}
		#endregion
        #region Podpora pro AddToolTip ukládaný do StringHeap pomocí Listu TwoInt32
        /// <summary>
        /// Vrátí string, který je uložen jako AddToolTip.
        /// Pokud je vrácen neprázdný string, pak má na začátku 1x CrLf.
        /// Pokud není uložen žádný AddToolTip, pak neobsahuje ani CrLf.
        /// </summary>
        /// <returns></returns>
        protected string AddToolTipGetText()
        {
            if (this.AddToolTipInfo == null) return String.Empty;
            string result = "";
            string eol = Environment.NewLine;
            foreach (TwoInt32 item in this.AddToolTipInfo)
            {
                string value1 = this.StringHeap.GetString(item.Value1);
                if (value1 == null) value1 = "";
                string value2 = this.StringHeap.GetString(item.Value2);
                if (value2 == null) value2 = "";
                string delimiter = ((value1.IsEmpty() || value2.IsEmpty()) ? "" : ": ");
                result += eol + value1 + delimiter + value2;
                if (value1.Contains(eol) || value2.Contains(eol) || value2.Length > 100)
                    result += eol;          // Pokud je v řádku obsažen EOL, anebo je řádek poněkud delší, dáme za něj ještě jeden EOL, aby za ním byl vizuální odstavec.
            }
            return result;
        }
		/// <summary>
		/// Přidá texty User(Nnn) do AddTooltipu
		/// </summary>
		/// <param name="item"></param>
		/// <param name="prefix"></param>
		protected virtual void AddToolTipAdd(TraceItem item, string prefix)
		{
			string pfx = (prefix.IsEmpty() ? "" : prefix + ".");         // prefix s tečkou na konci, anebo prázdný string
			for (int i = 0; i < item.UserLength; i++)
				this.AddToolTipAddTexts(pfx + "User" + i.ToString(), item.UserDataInt32[i]);
		}
		/// <summary>
        /// Do interní paměti přidaných textů pro ToolTip přidá další pár (hodnota 1, hodnota 2).
        /// Budou vypsány v jednom odděleném řádku, mezi nimi bude dvojtečka (pokud budou obě hodnoty neprázdné).
        /// </summary>
        /// <param name="value1">Typicky název proměnné (user1, FunctionNumber, atd), bez dvojtečky</param>
        /// <param name="value2">Typicky hodnota proměnné, bez dvojtečky, bez EOL</param>
        protected void AddToolTipAddTexts(string value1, string value2)
        {
            this._AddToolTipAddTexts(this.StringHeap.GetInt32(value1), this.StringHeap.GetInt32(value2));
        }
        /// <summary>
        /// Do interní paměti přidaných textů pro ToolTip přidá další pár (hodnota 1, hodnota 2).
        /// Budou vypsány v jednom odděleném řádku, mezi nimi bude dvojtečka (pokud budou obě hodnoty neprázdné).
        /// </summary>
        /// <param name="value1">Typicky název proměnné (user1, FunctionNumber, atd), bez dvojtečky</param>
        /// <param name="value2">Typicky hodnota proměnné, bez dvojtečky, bez EOL</param>
        protected void AddToolTipAddTexts(string value1, Int32 value2)
        {
            this._AddToolTipAddTexts(this.StringHeap.GetInt32(value1), value2);
        }
        /// <summary>
        /// Do interní paměti přidaných textů pro ToolTip přidá další pár (hodnota 1, hodnota 2).
        /// Budou vypsány v jednom odděleném řádku, mezi nimi bude dvojtečka (pokud budou obě hodnoty neprázdné).
        /// </summary>
        /// <param name="value1">Typicky název proměnné (user1, FunctionNumber, atd), bez dvojtečky</param>
        /// <param name="value2">Typicky hodnota proměnné, bez dvojtečky, bez EOL</param>
        protected void AddToolTipAddTexts(Int32 value1, string value2)
        {
            this._AddToolTipAddTexts(value1, this.StringHeap.GetInt32(value2));
        }
        /// <summary>
        /// Do interní paměti přidaných textů pro ToolTip přidá další pár (hodnota 1, hodnota 2).
        /// Budou vypsány v jednom odděleném řádku, mezi nimi bude dvojtečka (pokud budou obě hodnoty neprázdné).
        /// </summary>
        /// <param name="value1">Typicky název proměnné (user1, FunctionNumber, atd), bez dvojtečky</param>
        /// <param name="value2">Typicky hodnota proměnné, bez dvojtečky, bez EOL</param>
        protected void AddToolTipAddTexts(Int32 value1, Int32 value2)
        {
            this._AddToolTipAddTexts(value1, value2);
        }
        /// <summary>
        /// Do interní paměti přidaných textů pro ToolTip přidá další pár (hodnota 1, hodnota 2).
        /// Budou vypsány v jednom odděleném řádku, mezi nimi bude dvojtečka (pokud budou obě hodnoty neprázdné).
        /// </summary>
        /// <param name="value1">Typicky název proměnné (user1, FunctionNumber, atd), bez dvojtečky</param>
        /// <param name="value2">Typicky hodnota proměnné, bez dvojtečky, bez EOL</param>
        protected void _AddToolTipAddTexts(Int32 value1, Int32 value2)
        {
            if (this.AddToolTipInfo == null)
                this.AddToolTipInfo = new List<TwoInt32>();
            this.AddToolTipInfo.Add(new TwoInt32(value1, value2));
        }
        /// <summary>
		/// Tooltip k položce v grafu, ukládá se strukturovaně - proto, aby bylo možno jej ukládat ve StringHeap.
        /// </summary>
        protected List<TwoInt32> AddToolTipInfo { get; set; }
        /// <summary>
        /// class TwoInt32 pro ukládání dvou Int32 pro AddToolTipInfo.
        /// </summary>
        protected class TwoInt32
        {
            internal TwoInt32(int value1, int value2)
            {
                this.Value1 = value1;
                this.Value2 = value2;
            }
            internal Int32 Value1 { get; set; }
            internal Int32 Value2 { get; set; }
        }
        #endregion
		#region Úložiště boolean hodnot v jednom Int32
        /// <summary>
        /// Vrátí hodnotu Boolean z daného čísla bitu.
        /// Základní třída EventItem nechť využívá bity 0-15, potomek číslo 1 využívá bity 16 - 23, potomek číslo 2 bity 24 - 30.
        /// </summary>
        /// <param name="bite"></param>
        /// <returns></returns>
        protected bool BooleanGet(int bit)
        {
            return ((_BooleanValues & (1 << bit)) > 0);
        }
        /// <summary>
        /// Uloží hodnotu Boolean do daného čísla bitu.
        /// Základní třída EventItem nechť využívá bity 0-15, potomek číslo 1 využívá bity 16 - 23, potomek číslo 2 bity 24 - 30.
        /// </summary>
        /// <param name="bit"></param>
        /// <param name="value"></param>
        protected void BooleanSet(int bit, bool value)
        {
            int mask = 1 << bit;
            if (value)
                _BooleanValues = _BooleanValues | mask;
            else
                _BooleanValues = _BooleanValues & (mask ^ int.MaxValue);
        }
		/// <summary>
		/// Společné úložiště pro hodnoty Bool (jeden bit tohoto Int32 = jedna Bool hodnota
		/// </summary>
        private int _BooleanValues;
		#endregion
        #region Implementace interface IDataElement (namísto tvorby nového objektu, který by implementoval IDataElement budu já sám hrát roli IDataElement)
        bool IDataElement.ElementVisible { get { return this.ElementVisible && !this.MergedOut; } }      // Viditelnost může řídit potomek přes virtual property ElementVisible
        GID IDataElement.GId { get { return GID.Empty; } }                            // Zde není GID uložen, nemohu jej určit, vrátím Empty. On tento objekt de facto není plnohodnotný IDataElement (právě chybějícím GIDem), ale nechce se mi tvořit jiný interface, shodný s IDataElement až na nepřítomnost GIDu...
        TimeRange IDataElement.TimeRange { get { return this.ElementTime; } set { } } // Vracím čas elementu (buď nativní, nebo mergovaný)
        float IDataElement.ElementHeightRatio { get { return this.ElementHeightRatio; } }
        bool IDataElement.BeginIsUncertain { get { return this.ElementBeginIsUncertain; } }
        bool IDataElement.EndIsUncertain { get { return this.ElementEndIsUncertain; } }
        int IDataElement.SubTimeSort { get { return _GetSubTimeSort(); } }
		bool IDataElement.FullRow { get { return false; } }
        bool IDataElement.HideZeroWidthElementRectangle { get { return this.HideZeroWidthElementRectangle; } }
        float[] IDataElement.Fragments { get { return null; } }
        GraphLayerType IDataElement.ElementLayer { get { return GraphLayerType.ElementItemLayer; } }
        int IDataElement.ElementGroup { get { return this.ElementGroup; } }
        GraphElementShapeType IDataElement.ElementShapeType { get { return GraphElementShapeType.Hexagon; } }
        ElementProperties IDataElement.Properties { get { return this.ElementProperties; } }
        ElementActionType IDataElement.ActionOnDoubleClick { get { return ElementActionType.DefaultByRow; } }
        GraphElementHandler IDataElement.DoubleClick { get { return null; } }
        string IDataElement.ColorGroup { get { return this.ColorGroup; } }
        Color IDataElement.BackColor { get { return this.DetailBackColor; } set { } }
        Color IDataElement.FontColor { get { return Color.Empty; } }
        bool IDataElement.CaptionShowAllways { get { return true; } }
        Image IDataElement.ElementImage { get { return this.ElementImage; } }
        ContentAlignment IDataElement.ImagePivotElementPoint { get { return this.ImagePivotElementPoint; } }
        ContentAlignment IDataElement.ImagePivotImagePoint { get { return this.ImagePivotImagePoint; } }
        string[] IDataElement.Captions { get { return this.CreateCaptions(); } }
        string IDataElement.ToolTipInfo { get { return this.CreateToolTip(); } }

        string IDataElement.GetToolTipInfo(IDataRow rowData) { return null; }
        string IDataElement.GetCaption(IDataRow rowData, float charCount) { return null; }
        void IDataElement.DrawElement(GraphElementPainterArgs args) {  }

        #endregion
        #region Implementace interface IMasterDetails (event jako objekt, který má jméno a nese sadu detailů k jmenovitému zobrazení)
        string Forms.IMasterDetails.MasterName { get { return this.DetailMasterName; } }
        Color Forms.IMasterDetails.DetailBackColor { get { return Color.Empty; } }
		IEnumerable<MasterDetailItem> Forms.IMasterDetails.Details { get { return this.Details; } }
        protected abstract string DetailMasterName { get; }
		protected virtual IEnumerable<MasterDetailItem> Details
        { get { return this.CreateDetails(); } }
        /// <summary>
        /// Vytvoří, naplní a vrátí seznam informací Details.
        /// postupně volá metody DetailFillTraceTime(); DetailFill(); DetailFillUserData();
        /// </summary>
        /// <returns></returns>
		protected virtual IEnumerable<MasterDetailItem> CreateDetails()
        {
			List<MasterDetailItem> result = new List<MasterDetailItem>();
            this.DetailFillTraceTime(result);
            this.DetailFill(result);
            this.DetailFillUserData(result);
            return result;
        }
        /// <summary>
        /// Do seznamu detailních údajů vepíše položky ze své evidence.
        /// Třída EventItem vkládá tato data: "Trace, LineNumber"
        /// </summary>
        /// <param name="result"></param>
        protected virtual void DetailFillTraceTime(List<MasterDetailItem> result)
        {
			// result.Add(new MasterDetailItem("Trace", this.Trace.FileDirName));
            this.DetailFillTraceInfo("", this.Trace, this.LineNumber, result);
            result.Add(new MasterDetailItem("Time", this.Time.Begin.ToString("HH:mm:ss.fff")));
        }
        /// <summary>
        /// Do sady řádků (detaily) vloží data o daném trace.
        /// </summary>
        /// <param name="title">SubTitulek: například "", ".Begin", ".End" atd</param>
        /// <param name="traceInfo">Objekt TraceInfo. Může být null.</param>
        /// <param name="lineNumber">Číslo řádku. Pokud bude null, nebude se vypisovat.</param>
        /// <param name="result">Seznam řádků, kam se detaily vkládají.</param>
        protected void DetailFillTraceInfo(string title, TraceInfo traceInfo, Int32? lineNumber, List<MasterDetailItem> result)
        {
            if (traceInfo == null)
            {
                result.Add(new MasterDetailItem("Trace" + title, "???"));
            }
            else
            {
                result.Add(new MasterDetailItem("Trace" + title, traceInfo.FullFileName));
                if (lineNumber.HasValue)
                    result.Add(new MasterDetailItem("Trace" + title + ".Line", lineNumber.Value.ToString()));
            }
        }
        /// <summary>
        /// Do seznamu detailních údajů vepíše položky ze své evidence.
        /// Třída EventItem vkládá tato data: "SecureId, BEDepth, StatusInfo, Login, Stack, ThreadId, RoundtripId, PageId, Type, Method, Keyword"
        /// </summary>
        /// <param name="result"></param>
        protected virtual void DetailFill(List<MasterDetailItem> result)
        {
            result.Add(new MasterDetailItem("BEDepth", this.BEDepth));
            result.Add(new MasterDetailItem("Login", this.Login));
            result.Add(new MasterDetailItem("Stack", this.CreateDetailStack()));
            result.Add(new MasterDetailItem("ThreadId", this.ThreadId));
            result.Add(new MasterDetailItem("RoundtripId", this.RoundtripId));
            result.Add(new MasterDetailItem("PageId", this.PageId));
            result.Add(new MasterDetailItem("Keyword", this.Keyword));
            result.Add(new MasterDetailItem("Method", this.Method));
            result.Add(new MasterDetailItem("Type", this.Type));
            result.Add(new MasterDetailItem("SecureId", this.SecureId));
            result.Add(new MasterDetailItem("StatusInfo", this.StatusInfo.ToString()));
        }
        /// <summary>
        /// Vrátí přeformátovaný text Stack.
        /// </summary>
        /// <returns></returns>
        protected string CreateDetailStack()
        {
            string result = "";
            if (this.Stack != null)
            {
                // 1. Výchozí text stacku je v opačném pořadí, než je pořadí srozumitelné. 
                //    Obrátíme pořadí tak, aby bylo: Main => Obsluha => Jádro => volá funkci => ta volá další funkci ... => nejnižší privátní metody:
                string[] stackItems = this.Stack.Split('-');
                for (int s = stackItems.Length - 1; s >= 0; s--)
                    result += (result.Length > 0 ? "; " : "") + stackItems[s];

                // 2. Ve stacku najdu předem definované texty, a ty vyčlením do samostatného řádku = tím oddělím například jednotlivé funkce:
                result = CreateDetailStackRow(result, "ExecuteMenuFunction; RunFunctionVisually; _RunFunctionVisually; RunFunction; ");
                result = CreateDetailStackRow(result, "ExecuteMenuAction; _RunAction; MenuAction; MenuAction; ");
                result = CreateDetailStackRow(result, "ExecuteMenuAction; ExecuteMenuAction; _RunFunction; RunFunctionVisually; RunFunctionVisually; _RunFunctionVisually; RunFunction; ");
                result = CreateDetailStackRow(result, "Run; _Run1_nonvisualPart; _Run2_progressbarPart; _Run3_rundonePart; _Run4_auditedPart; _Run5_invokePart; Invoke; Invoke; Invoke; InvokeMethodFast; _InvokeMethodFast; ");
                result = CreateDetailStackDup(result);
            }
            return result;
        }
        /// <summary>
        /// Vrátí vstupní text, v němž najde daný pattern, a vyčlení jej na samostatný řádek.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="pattern"></param>
        /// <returns></returns>
        private string CreateDetailStackRow(string text, string pattern)
        {
            if (String.IsNullOrEmpty(pattern) || !text.Contains(pattern)) return text;

            string pre = Environment.NewLine + "    ";
            string aft = Environment.NewLine + "  ";
            string add = "  ";

            if (text.Contains(pre + pattern + aft))                // Pokud už řádek je plně oddělen, nic s ním nedělám.
                return text;
            //if (text.Contains(pre + pattern))                      // Pokud je řádek oddělen na svém začátku (ale není oddělen na konci = viz předešlá podmínka), přidám oddělení na konci.
            //    return text.Replace(pattern, pattern + aft);
            //if (text.Contains(aft + pattern + aft))                // Pokud je řádek oddělen na začátku krátkou mezerou, a má ji i na konci, pak jen na začátek přidám doplnění mezery.
            //    return text.Replace(pattern, add + pattern);
            //if (text.Contains(aft + pattern))                      // Pokud je na začátku krátké oddělení, a na konci není, přidám doplnění mezery a oddělovač na konci.
            //    return text.Replace(pattern, add + pattern + aft);
            return text.Replace(pattern, pre + pattern + aft);
        }
        /// <summary>
        /// Z daného řádku odebere prázdné řádky.
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        private string CreateDetailStackDup(string text)
        {
            List<string> lines = text.ToLines(true);
            string result = lines.ToStringList(Environment.NewLine);
            return result;
        }
        /// <summary>
        /// Do seznamu detailních údajů vepíše položky ze své evidence, hodnoty pouze UserData[xxx].
        /// </summary>
        /// <param name="result"></param>
        protected virtual void DetailFillUserData(List<MasterDetailItem> result)
        {
            for (int u = 0; u < this.UserLength; u++)
                result.Add(new MasterDetailItem("User" + u.ToString(), this.UserData(u)));
        }
		/// <summary>
		/// String pro formátování času (bez data): HH:mm:ss.fff
		/// </summary>
		protected const string TIME_FMT = "HH:mm:ss.fff";
		/// <summary>
		/// String pro formátování desetinných čísel na 3 desetinná místa: ### ##0.000
		/// </summary>
		protected const string SECONDS_FMT = "### ##0.000";
        #endregion
        #region Komparátory, třídičky
        /// <summary>
        /// Setřídí události podle TimeRange.Begin ASC, SubTimeSort ASC, ElementGroup ASC, TimeRange.End DESC
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static int CompareByBeginSubGroupEnd(IEventItem a, IEventItem b)
        {
            TimeRange at = a.TimeRange;
            TimeRange bt = b.TimeRange;
            
            int cmp = at.Begin.CompareTo(bt.Begin);
            if (cmp != 0) return cmp;

            cmp = a.SubTimeSort.CompareTo(b.SubTimeSort);
            if (cmp != 0) return cmp;

            cmp = a.ElementGroup.CompareTo(b.ElementGroup);
            if (cmp != 0) return cmp;
            
            return bt.End.CompareTo(at.End);
        }
        #endregion
    }
    #endregion
	#region EventSystemItem : data o systémové události (nepárový záznam v trace), její Time je s nulovým časem
	/// <summary>
	/// EventSystemItem : data o systémové události (nepárový záznam v trace), její Time je s nulovým časem
    /// </summary>
    public class EventSystemItem : EventItem
	{
		#region Static detekce RegisterFor()
		/// <summary>
        /// Zde potomek určí, zda on je vhodný pro daný trace řádek
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
		public static bool RegisterFor(TraceItem item)
        {
            return (item.BSE == LineBSE.System);
        }
        #endregion
		#region Konstrukce, Property specifické pro System
		protected EventSystemItem(StringHeap stringHeap, TraceItem item)
            : base(stringHeap, item)
        {
            this.OneDimensional = true;
        }
		public static EventItem CreateEvent(StringHeap stringHeap, TraceItem item)
        {
            EventSystemItem info = new EventSystemItem(stringHeap, item);
            return info;
        }
        public override string ToString()
        {
            return "EventSystem: Time=" + this.Time.Begin.ToString() + "; Level=" + this.Level.ToString() + "; Keyword=" + this.Keyword + "; Type=" + this.Type + "; Method=" + this.Method;
        }
        #endregion
        #region Override abstract metody, protected virtual háčky
        /// <summary>
        /// Základní metoda, skrze kterou vstupují data z Item do Event
        /// </summary>
        /// <param name="item"></param>
        /// <param name="traceInfo"></param>
        protected override void AddTraceItem(TraceItem item, TraceInfo traceInfo)
		{
            // Čas je bezrozměrový, délka = 0:
			this.Time = new TimeRange(item.Time, item.Time);
			// Nevoláme base.AddTraceItem(), děláme si to sami a jinak:
            this.StoreItemBegin(item, traceInfo, false);
            this.AddToolTipAddTexts("Stack", item.StackInt32);
			this.AddToolTipAdd(item, "");

            // Obrázek, zarovnání:
			switch (item.Level)
			{
				case LineIWE.Information:
                    this.StatusInfo = StatusInfoType.Information;
					this.ElementImage = TraceVisualiser.Pics.PicLibrary16.Dialoginformation3_Cached;
                    this.HideZeroWidthElementRectangle = true;
					break;
				case LineIWE.Warning:
                    this.StatusInfo = StatusInfoType.Warning;
                    this.ElementImage = TraceVisualiser.Pics.PicLibrary16.Warning_16_Cached;
                    this.HideZeroWidthElementRectangle = true;
					break;
				case LineIWE.Error:
                    // Zde zkusím detekovat například deadlock:
					if (item.IsDeadlock)
                    {
                        this.StatusInfo = StatusInfoType.Deadlock;
                        this.ElementImage = TraceVisualiser.Pics.PicLibrary16.LockRed3_Cached;
                        this.HideZeroWidthElementRectangle = true;
                    }
                    else
                    {
                        this.StatusInfo = StatusInfoType.Error;
                        this.ElementImage = TraceVisualiser.Pics.PicLibrary16.Error_16_Cached;
                        this.HideZeroWidthElementRectangle = true;
                    }
					break;
			}
            this.ImagePivotElementPoint = ContentAlignment.MiddleCenter;
            this.ImagePivotImagePoint = ContentAlignment.MiddleCenter;

			this.StoreLineAfter(item);
        }
        /// <summary>
        /// Háček pro potomky, kteří by chtěli ukládat další data.
        /// Bázová metoda ukládá UserData.
        /// Potomek bázovou metodu volat nemusí, pokud UserData zpracuje do svých property.
        /// </summary>
        /// <param name="line"></param>
		protected virtual void StoreLineAfter(TraceItem item)
        {
			this.UserDataStore(item);
        }
		/// <summary>
		/// Tento element je vždy viditelný
		/// </summary>
		protected override bool ElementVisible
		{
			get { return (this.ElementImage != null); }
			set { }
		}
        #endregion
        #region Texty do elementu (Caption, Tooltip)
        /// <summary>
        /// Zde potomek musí vytvořit souhrn textů pro zobrazení uvnitř elementu
        /// </summary>
        /// <returns></returns>
        protected override string[] CreateCaptions()
        {
            return new string[] 
            {
                this.Method,
                this.Keyword + ":" + this.Method,
                this.Keyword + ":" + this.Method + " (" + this.Type + ")"
            };
        }
        /// <summary>
        /// Zde potomek vytvoří tooltip
        /// </summary>
        /// <returns></returns>
        protected override string CreateToolTip()
        {
            string text =
                    CaptionMaxima + 
                   "Keyword: " + this.Keyword + Environment.NewLine +
                   "Method: " + this.Method + Environment.NewLine +
                   "Type: " + this.Type + Environment.NewLine +
                   "Time: " + this.ElementTime.StringFullDetail + Environment.NewLine +
                   Environment.NewLine +
                   "ThreadId: " + this.ThreadId.ToString() + "; RoundtripId: " + this.RoundtripId.ToString() + "; PageId: " + this.PageId.ToString() + Environment.NewLine +
                   "Trace: " + (this.Trace == null ? "???" : this.Trace.FileDirName) + "; #" + this.LineNumber.ToString() + Environment.NewLine;

            text += this.AddToolTipGetText();

            return text;
        }
        /// <summary>
        /// Metoda má za úkol vrátit klíčové slovo sebe sama, které bude hrát roli v ColorGroup. Slovo má mít na začátku tečku!!!
        /// Metoda se volá jen pro některá klíčová slova.
        /// </summary>
        /// <returns></returns>
        protected override string GetColorGroupKeyword()
        {
            return "";   
        }
        #endregion
        #region Texty do okna Detail
        protected override string DetailMasterName
        {
            get { return this.LineNumber.ToString() + ": " + this.Time.Begin.ToString("HH:mm:ss.fff") + "; " + this.Keyword; }
        }
        #endregion
    }
    #endregion
    #region EventTransItem: data o události TRANS, pocházejí z nepárového Itemu, ale lze je mergovat
    /// <summary>
	/// EventTransItem: data o události TRANS, pocházejí z nepárového Itemu, ale lze je mergovat do jednoho párového eventu
    /// </summary>
    public class EventTransItem : EventSystemItem
    {
        #region Static detekce RegisterFor()
        /// <summary>
        /// Zde potomek určí, zda on je vhodný pro daný trace řádek
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
		public static new bool RegisterFor(TraceItem item)
        {
			// Tento objekt zpracovává pouze transakce, metody Begin, End(Begin), End(End) (nikoli _CheckState):
			string method = item.Method.Trim().ToLower();
			if (item.Keyword == "TRANS" && (method == "begin" || method == "end(begin)" || method == "end(end)"))
				return true;
			return false;
        }
		#endregion
		#region Konstrukce, Property specifické pro SQL select
		protected EventTransItem(StringHeap stringHeap, TraceItem item)
            : base(stringHeap, item)
        {
            // Mergovat mohu pouze události typu Begin, End(Begin) a End(End):
            TransactionChangeType transChange = GetTransChange(item.Method);
            if (transChange == TransactionChangeType.Begin || transChange == TransactionChangeType.Commit || transChange == TransactionChangeType.End)
    			this.MergeKey = "TRANS" + "_" +  /* item.User(0) + "." + */  item.PageId.ToString();
        }
		public static EventItem CreateEvent(StringHeap stringHeap, TraceItem item)
        {
			EventTransItem info = new EventTransItem(stringHeap, item);
            return info;
        }
        public override void Dispose()
        {
            base.Dispose();
            this.TraceEnd = null;
        }
        public override string ToString()
        {
			return "EventTrans: Time=" + this.Time.StringBeginAndSecs + "; Method=" + this.Method + "; TransChange=" + this.TransChange.ToString();
        }
        public TraceInfo TraceEnd { get; protected set; }
        public int LineNumberEnd { get; protected set; }
        /// <summary>
        /// Číslo SQL procesu
        /// </summary>
        public int SqlPId { get; protected set; }
        /// <summary>
        /// Druh connection
        /// </summary>
		public string SqlConnect { get { return this.StringHeap.GetString(SqlConnectInt32); } }
		public Int32 SqlConnectInt32 { get; set; }
        /// <summary>
        /// Aktuální úroveň zanoření transakce
        /// </summary>
        public int TransLevelCurrent { get; protected set; }
        /// <summary>
        /// Počitadlo transakce, bez významu
        /// </summary>
		public int TransInternalId { get; protected set; }
		public string SqlCommit { get { return this.StringHeap.GetString(SqlCommitInt32); } }
		public Int32 SqlCommitInt32 { get; set; }
        /// <summary>
        /// Stav transakce načtený z trace, průběžně aktualizovaný při Mergování
        /// </summary>
		public TransactionChangeType TransChange { get; protected set; }
        #endregion
        #region Override abstract metody, protected virtual háčky
        /// <summary>
        /// Základní metoda, skrze kterou vstupují směrodatná data z položky trace souboru.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="traceInfo"></param>
        protected override void AddTraceItem(TraceItem item, TraceInfo traceInfo)
        {
            // Čas je bezrozměrový:
            this.Time = new TimeRange(item.Time, item.Time);
            this.StoreItemBegin(item, traceInfo, false);           // Line, Trace, Keywords, atd
            this.StatusInfo = StatusInfoType.Event;                // Tím se lišíme od System: nemáme Info/Warning/Error, ale Event

            this.StoreLineAfter(item);            
        }
		/// <summary>
		/// Háček pro potomky, kteří by chtěli ukládat další data.
		/// Bázová metoda ukládá UserData.
		/// Potomek bázovou metodu volat nemusí, pokud UserData zpracuje do svých property.
		/// </summary>
		/// <param name="line"></param>
		protected override void StoreLineAfter(TraceItem item)
		{
			this.TransChange = TransactionChangeType.None;

			string method = item.Method.ToLower().Trim();            // Zde se zpracovávají pouze metody: Begin, End(Begin), End(End) (nikoli _CheckState) - viz this:RegisterFor()
			int userLength = item.UserLength;

			if (userLength >= 4)
			{
				int sqlPId;
				if (Int32.TryParse(item.User(0), out sqlPId))
					this.SqlPId = sqlPId;

				this.SqlConnectInt32 = item.UserDataInt32[1];        // SQLFORM / SQLCA

				if (userLength >= 5)
					this.SqlCommitInt32 = item.UserDataInt32[4];     // V řádku s metodou End(Begin) zde je text "COMMIT"

				int level, id;
				bool isLevel = Int32.TryParse(item.User(2), out level);  // Level vnoření transakce (začíná 1 po Begin, 
				bool isId = Int32.TryParse(item.User(3), out id);        // Pořadové číslo trace řádku v rámci jedné transakce (
				if (isLevel && isId)
				{
					this.TransLevelCurrent = level;
					this.TransInternalId = id;
                    TransactionChangeType transChange = GetTransChange(method);
					if (level == 1 && transChange == TransactionChangeType.Begin)
						this.TransChange = TransactionChangeType.Begin;
                    else if (level == 0 && transChange == TransactionChangeType.Commit)
						this.TransChange = TransactionChangeType.Commit;
					else if (level == 0 && transChange == TransactionChangeType.End)
						this.TransChange = TransactionChangeType.End;
				}
			}
		}
        /// <summary>
        /// Vrátí stav transakce pouze podle Method. Nereflektuje hloubku DbTrans. Jen překládá text na typ.
        /// </summary>
        /// <param name="method"></param>
        /// <returns></returns>
        protected TransactionChangeType GetTransChange(string method)
        {
            if (String.IsNullOrEmpty(method)) return TransactionChangeType.None;
            switch (method.ToLower())
            {
                case "begin":
                    return TransactionChangeType.Begin;
                case "end(begin)":
                    return TransactionChangeType.Commit;
                case "end(end)":
                    return TransactionChangeType.End;
                case "_checkstate":
                    return TransactionChangeType.CheckState;
            }
            return TransactionChangeType.None;
        }
        /// <summary>
        /// Barevná skupina transakce je daná jejím stavem.
        /// Bez ohledu na výsledky metody EventItem.FillVisualTargets(), protože property ColorGroup je virtuální.
        /// </summary>
        public override string ColorGroup { get { return "Trans." + this.TransChange.ToString(); } }
        /// <summary>
        /// Tento element je vždy viditelný
        /// </summary>
        protected override bool ElementVisible
        {
            get { return true; }
            set { }
        }
        #endregion
        #region Texty do elementu (Caption, Tooltip)
        /// <summary>
        /// Zde potomek musí vytvořit souhrn textů pro zobrazení uvnitř elementu
        /// </summary>
        /// <returns></returns>
        protected override string[] CreateCaptions()
        {
            string spid = " (SPID: " + this.SqlPId.ToString() + ")";
            return new string[] 
            {
                "DbTrans",
                "DbTrans " + spid,
                "DbTrans " + spid + ", " + this.TransChange.ToString()
            };
        }
        /// <summary>
        /// Zde potomek vytvoří tooltip
        /// </summary>
        /// <returns></returns>
        protected override string CreateToolTip()
        {
            string text =
                    CaptionMaxima +
                   "Keyword: " + this.Keyword + Environment.NewLine +
                   "Method: " + this.Method + Environment.NewLine +
                   "Type: " + this.Type + Environment.NewLine +
                   "Time: " + this.ElementTime.StringFullDetail + Environment.NewLine +
                   "User: " + this.Login + Environment.NewLine +
                   Environment.NewLine +
                   "ThreadId: " + this.ThreadId.ToString() + "; RoundtripId: " + this.RoundtripId.ToString() + "; PageId: " + this.PageId.ToString() + Environment.NewLine +
                   "Trace Begin: " + (this.Trace == null ? "???" : this.Trace.FileDirName) + "; #" + this.LineNumber.ToString() + Environment.NewLine +
                   "Trace End: " + (this.TraceEnd == null ? "???" : this.TraceEnd.FileDirName) + "; #" + this.LineNumberEnd.ToString() + Environment.NewLine +
                   Environment.NewLine +
                   "SqlConnect: " + this.SqlConnect + Environment.NewLine +
                   "TranLevel: " + this.TransLevelCurrent.ToString() + Environment.NewLine +
                   "TranId: " + this.TransInternalId.ToString();
            return text;
        }
        /// <summary>
        /// Metoda má za úkol vrátit klíčové slovo sebe sama, které bude hrát roli v ColorGroup. Slovo má mít na začátku tečku!!!
        /// Metoda se volá jen pro některá klíčová slova.
        /// </summary>
        /// <returns></returns>
        protected override string GetColorGroupKeyword()
        {
            switch (this.TransChange)
            {
                case TransactionChangeType.Begin:
                    return ".begin";
                case TransactionChangeType.Commit:
                    return ".commit";
                case TransactionChangeType.Rollback:
                    return ".rollback";
            }
            return "";
        }
        #endregion
        #region Podpora mergování elementů
		/// <summary>
		/// Dotaz, zda lze položku nextEvent připojit do položky this (pak by položka this byla výsledkem, do něhož se přidá nextEvent).
		/// Toto je pouze detekční metoda, teprve po vrácení hodnoty true budou volány metody MergeInWithThing() a MergeOutIntoThing().
		/// </summary>
		/// <param name="nextEvent">Následující položka</param>
		/// <returns></returns>
		public override bool MergeWith(EventItem nextEvent)
		{
			// Pokud druhá událost není jako my (ThingPairCowleyInfo), pak ji nechci:
			EventTransItem nextTrans = nextEvent as EventTransItem;
			if (nextTrans == null)
				return false;

            // Máme za sebou dvě události typu Trans, v témže procesu a se shodným klíčem MergeKey (proto se dostalo volání sem, to zajišťuje datová vrstva), a jsou seřazené časově po sobě.
			// Mergovat mohu pouze událost s vyšší hodnotou TransInternalId do události s nižší TransInternalId:
			if (nextTrans.TransInternalId < this.TransInternalId)
				return false;

			// Pokud mi přichází nová událost typu Begin, a zdejší událost už je ve stavu TransChange == Commit / Roolback / End,
			//  pak tuto novou událost už nemohu přijmout:
			if (nextTrans.TransChange == TransactionChangeType.Begin && nextTrans.TransLevelCurrent == 1 &&
				(this.TransChange == TransactionChangeType.Commit || this.TransChange == TransactionChangeType.Rollback || this.TransChange == TransactionChangeType.End))
				return false;

            // Pokud načítám "doleva" (tj. mám načten čas 10, a načítám předchozí čas 9),
            //  pak mohu mít již zkompletovánu transakci v čase 10 (ačkoliv nemusí mít zcela jistý Begin),
            //  ale pokud mám transakci v čase 9 (tj. objekt this) již dokončenou (Commit / Rollback / End), 
            //  pak do ní nemohu přimergovat transakci budoucí:
            if ((this.TransChange == TransactionChangeType.Commit && nextTrans.TransChange == TransactionChangeType.Commit) ||
                (this.TransChange == TransactionChangeType.End && (nextTrans.TransChange == TransactionChangeType.Commit || nextTrans.TransChange == TransactionChangeType.End)))
                return false;

			// Sloučit:
			Merge(this, nextTrans);

			return true;
		}
		/// <summary>
		/// Zajistí sloučení dvou objektů do jednoho (do toho prev).
		/// </summary>
		/// <param name="prev"></param>
		/// <param name="next"></param>
		internal static void Merge(EventTransItem prev, EventTransItem next)
		{
			// Sloučení času:
            //  Pozor: tento druh eventu neřeším jako párové eventy, kde se volá prev.MergeEvent(next), protože je to zbytečná komplikace.
            //  Metoda prev.MergeEvent(next) představuje řešení pro komplikovanější stavy, kdy se mergují elementy s nekompletními časy!
            prev.Time = prev.Time + next.Time;

			// Pokud next událost není finální (TransChange není Commit / End), pak z ní nebudu načítat nic dalšího:
			if (next.TransChange == TransactionChangeType.Commit)
				// Skutečný Commit => vždy nastaví Commit transakce:
				prev.TransChange = TransactionChangeType.Commit;
			else if (next.TransChange == TransactionChangeType.End)
			{	// Skutečný End transakce => pokud není mergovaný stav Commit, nastaví Rollback:
				if (prev.TransChange != TransactionChangeType.Commit)
					prev.TransChange = TransactionChangeType.Rollback;
			}
			else
				// Jiný stav než Commit nebo End => nic dalšího z něj načítat nebudu:
				return;

			// Převezmu do prev události hodnotu TransLevelCurrent z next události: tato hodnota odráží průběžně se měnící hladinu vnoření transakce:
			prev.TransLevelCurrent = next.TransLevelCurrent;
			prev.TransInternalId = next.TransInternalId;
            prev.TraceEnd = next.Trace;
            prev.LineNumberEnd = next.LineNumber;
		}
		#endregion
		#region Texty do okna Detail
		protected override string DetailMasterName
		{
            get { return this.LineNumber.ToString() + ": " + this.ElementTime.Begin.ToString(TIME_FMT) + " + " + this.ElementTime.Seconds.ToString(SECONDS_FMT) + " sec; " + this.Keyword; }
		}
		/// <summary>
		/// Do seznamu detailních údajů vepíše položky ze své evidence.
		/// Třída EventTransItem vkládá tato data: "Begin.Trace, Begin.LineNumber, End.Trace, End.LineNumber, Time (interval)"
		/// </summary>
		/// <param name="result"></param>
		protected override void DetailFillTraceTime(List<MasterDetailItem> result)
		{
            this.DetailFillTraceInfo(".Begin", this.Trace, this.LineNumber, result);
            this.DetailFillTraceInfo(".End", this.TraceEnd, this.LineNumberEnd, result);
			result.Add(new MasterDetailItem("Time", this.ElementTime.StringFullDetail, true, false));
            result.Add(new MasterDetailItem("Begin.Time", this.ElementTime.Begin.ToString(TIME_FMT), false, true));
            result.Add(new MasterDetailItem("End.Time", this.ElementTime.End.ToString(TIME_FMT), false, true));
            result.Add(new MasterDetailItem("Seconds", this.ElementTime.Seconds.ToString(SECONDS_FMT), false, true));
		}
		/// <summary>
		/// Do seznamu detailních údajů vepíše položky ze své evidence.
		/// Třída EventItem vkládá tato data: "SecureId, BEDepth, StatusInfo, Login, Stack, ThreadId, RoundtripId, PageId, Type, Method, Keyword"
		/// Třída EventTransItem přidává "SqlPId".
		/// </summary>
		/// <param name="result"></param>
		protected override void DetailFill(List<MasterDetailItem> result)
		{
			base.DetailFill(result);

			result.Add(new MasterDetailItem("SqlPId", this.SqlPId));
		}
		#endregion
    }
    #endregion
	#region EventPairItem : data o události, která má začátek i konec (Begin-End)
	/// <summary>
	/// EventPairItem : data o události, která má začátek i konec (Begin-End)
    /// </summary>
    public class EventPairItem : EventItem
    {
        #region Static detekce RegisterFor()
        /// <summary>
        /// Zde potomek určí, zda on je vhodný pro daný trace řádek
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
		public static bool RegisterFor(TraceItem item)
        {
			return (item.BSE == LineBSE.Begin || item.BSE == LineBSE.End);
        }
        #endregion
		#region Konstrukce, Property specifické pro Pair
        /// <summary>
        /// Reference na objekt Trace, v němž je END události
        /// </summary>
        public TraceInfo TraceEnd { get; protected set; }
        public int LineNumberEnd { get; protected set; }
        /// <summary>
        /// Kvůli technice IPlugin musím nabízet alespoň privátní bezparametrický konstruktor:
        /// </summary>
        protected EventPairItem() { }
        /// <summary>
        /// Standardní konstruktor
        /// </summary>
        /// <param name="stringHeap"></param>
        /// <param name="item"></param>
		protected EventPairItem(StringHeap stringHeap, TraceItem item)
            : base(stringHeap, item)
        {
            this.OneDimensional = false;
            // Začátek i konec je nejistý, dokud nenajdu explicitní záznamy Begin / End:
            this.BeginIsUncertain = true;
            this.EndIsUncertain = true;
        }
		public static EventItem CreateEvent(StringHeap stringHeap, TraceItem item)
        {
            EventPairItem info = new EventPairItem(stringHeap, item);
            return info;
        }
        public override void Dispose()
        {
            base.Dispose();
            this.TraceEnd = null;
        }
        public override string ToString()
        {
            return "EventPair: Time=" + this.Time.Begin.ToString() + " + " + (1000D * this.Time.Seconds).ToString() + " milisec; Keyword=" + this.Keyword + "; Type=" + this.Type + "; Method=" + this.Method;
        }
        #endregion
		#region Override abstract metody, protected virtual háčky
        protected override void AddTraceItem(TraceItem item, TraceInfo traceInfo)
        {
			switch (item.BSE)
            {
                case LineBSE.Begin:
                    this.StoreItemBegin(item, traceInfo, false);
                    this.StoreLineBegin(item, traceInfo);
                    break;
                case LineBSE.End:
                    this.StoreItemBegin(item, traceInfo, true);
                    this.StoreLineEnd(item, traceInfo);
                    break;
            }
        }
        /// <summary>
        /// Ukládá data z řádku Begin
        /// </summary>
        /// <param name="line"></param>
        protected virtual void StoreLineBegin(TraceItem item, TraceInfo traceInfo)
        {
			this.Time = new TimeRange(item.Time, this.Time.End);
            this.BeginIsUncertain = false;
            this.AddToolTipAdd(item, "Begin");
			this.StoreLineBeginAfter(item, traceInfo);
        }
        /// <summary>
        /// Ukládá data z řádku End
        /// </summary>
        /// <param name="line"></param>
        protected virtual void StoreLineEnd(TraceItem item, TraceInfo traceInfo)
        {
			this.Time = new TimeRange(this.Time.Begin, item.Time);
            this.LineNumberEnd = item.LineNumber;
            this.TraceEnd = traceInfo;
            this.EndIsUncertain = false;
            this.AddToolTipAdd(item, "End");
			this.StoreLineEndAfter(item, traceInfo);
        }
        /// <summary>
        /// Háček pro potomky, kteří by chtěli ukládat další data.
        /// Bázová metoda ukládá UserData.
        /// Potomek bázovou metodu volat nemusí, pokud UserData zpracuje do svých property.
        /// Pozor: bázová metoda ukládá data pouze z řádku BEGIN, ale z řádku END se UserData neukládají!
        /// </summary>
        /// <param name="line"></param>
        protected virtual void StoreLineBeginAfter(TraceItem item, TraceInfo traceInfo)
        {
			this.UserDataStore(item);
        }
        /// <summary>
        /// Háček pro potomky, kteří by chtěli ukládat další data.
        /// Pozor: bázová metoda nic nedělá, tedy ani neukládá UserData.
        /// Pokud potomek chce uložit UserData z věty End, pak může zavolat metodu this.UserDataStore(line);
        /// </summary>
        /// <param name="line"></param>
        protected virtual void StoreLineEndAfter(TraceItem item, TraceInfo traceInfo)
        {
        }
        /// <summary>
        /// Tento element je viditelný (pokud má kladný čas)
        /// </summary>
        protected override bool ElementVisible
        {
            get { return true; }        // nikoliv:    this.Time.Seconds >= 0D
            set { }
        }
        #endregion
        #region Texty do elementu (Caption, Tooltip)
        /// <summary>
        /// Zde potomek vytvoří souhrn textů pro zobrazení uvnitř elementu
        /// </summary>
        /// <returns></returns>
        protected override string[] CreateCaptions()
        {
            return new string[] 
            {
                this.Method,
                this.Keyword + ":" + this.Method,
                this.Keyword + ":" + this.Method + " (" + this.Type + ")"
            };
        }
        /// <summary>
        /// Zde potomek vytvoří tooltip
        /// </summary>
        /// <returns></returns>
        protected override string CreateToolTip()
        {
            string text =
                    CaptionMaxima +
                   "Keyword: " + this.Keyword + Environment.NewLine +
                   "Method: " + this.Method + Environment.NewLine +
                   "Type: " + this.Type + Environment.NewLine +
                   "Time: " + this.ElementTime.StringFullDetail + Environment.NewLine +
                   Environment.NewLine +
                   "ThreadId: " + this.ThreadId.ToString() + "; RoundtripId: " + this.RoundtripId.ToString() + "; PageId: " + this.PageId.ToString() + Environment.NewLine +
                   "Trace Begin: " + (this.Trace == null ? "???" : this.Trace.FileDirName) + "; #" + this.LineNumber.ToString() + Environment.NewLine +
                   "Trace End: " + (this.TraceEnd == null ? "???" : this.TraceEnd.FileDirName) + "; #" + this.LineNumberEnd.ToString() + Environment.NewLine;

            text += this.AddToolTipGetText();

            return text;
        }
        /// <summary>
        /// Metoda má za úkol vrátit klíčové slovo sebe sama, které bude hrát roli v ColorGroup. Slovo má mít na začátku tečku!!!
        /// Metoda se volá jen pro některá klíčová slova.
        /// </summary>
        /// <returns></returns>
        protected override string GetColorGroupKeyword()
        {
            return "";
        }
        #endregion
        #region Texty do okna Detail
        protected override string DetailMasterName
        {
            get { return this.LineNumber.ToString() + ": " + this.ElementTime.Begin.ToString(TIME_FMT) + " + " + this.ElementTime.Seconds.ToString(SECONDS_FMT) + " sec; " + this.Keyword; }
        }
        /// <summary>
        /// Do seznamu detailních údajů vepíše položky ze své evidence.
        /// Třída EventPairItem vkládá tato data: "Begin.Trace, Begin.LineNumber, End.Trace, End.LineNumber, Time (interval)"
        /// </summary>
        /// <param name="result"></param>
        protected override void DetailFillTraceTime(List<MasterDetailItem> result)
        {
            this.DetailFillTraceInfo(".Begin", this.Trace, this.LineNumber, result);
            this.DetailFillTraceInfo(".End", this.TraceEnd, this.LineNumberEnd, result);
            result.Add(new MasterDetailItem("Time", this.ElementTime.StringFullDetail, true, false));
			result.Add(new MasterDetailItem("Begin.Time", this.ElementTime.Begin.ToString(TIME_FMT), false, true));
            result.Add(new MasterDetailItem("End.Time", this.ElementTime.End.ToString(TIME_FMT), false, true));
            result.Add(new MasterDetailItem("Seconds", this.ElementTime.Seconds.ToString(SECONDS_FMT), false, true));
        }
        #endregion
        #region Podpora mergování elementů
        /// <summary>
        /// Zajistí mergování informací o trace souborech do this eventu.
        /// V this eventů bude uchována informace o nejčasnějším začátku a o nejpozdějším konci události z obou mergovaných událostí.
        /// </summary>
        /// <param name="next"></param>
        protected virtual void MergeTrace(EventCowleyItem next)
        {
            bool mergeBegin = (next.Trace != null && (this.Trace == null || (this.Trace != null && (next.Trace.Time.Begin < this.Trace.Time.Begin || (next.Trace.Id == this.Trace.Id && next.LineNumber < this.LineNumber)))));
            bool mergeEnd = (next.TraceEnd != null && (this.TraceEnd == null || (this.TraceEnd != null && (next.TraceEnd.Time.End > this.TraceEnd.Time.End || (next.TraceEnd.Id == this.TraceEnd.Id && next.LineNumberEnd > this.LineNumberEnd)))));
            if (mergeBegin)
            {
                this.Trace = next.Trace;
                this.LineNumber = next.LineNumber;
            }
            if (mergeEnd)
            {
                this.TraceEnd = next.TraceEnd;
                this.LineNumberEnd = next.LineNumberEnd;
            }
        }
        #endregion
    }
    #endregion
	#region EventSqlItem : data o události SQL
	/// <summary>
	/// EventSqlItem : data o události SQL
    /// </summary>
    public class EventSqlItem : EventPairItem
    {
        #region Static detekce RegisterFor()
        /// <summary>
        /// Zde potomek určí, zda on je vhodný pro daný trace řádek
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
		public static new bool RegisterFor(TraceItem item)
        {
			if ((item.BSE == LineBSE.Begin || item.BSE == LineBSE.End) && item.Keyword == "SQL" && item.UserLength >= 2) return true;
			return false;
        }
		#endregion
		#region Konstrukce, Property specifické pro SQL select
		protected EventSqlItem(StringHeap stringHeap, TraceItem item)
            : base(stringHeap, item)
        {
        }
		public static EventItem CreateEvent(StringHeap stringHeap, TraceItem item)
        {
            EventSqlItem info = new EventSqlItem(stringHeap, item);
            return info;
        }
        public override string ToString()
        {
            return "EventSql: Time=" + this.Time.Begin.ToString() + " + " + (1000D * this.Time.Seconds).ToString() + " milisec; Keyword=" + this.Keyword + "; Type=" + this.Type + "; Method=" + this.Method + "; SQL: " + this.SqlCommand.ToStringNull();
        }
        public int SqlPId { get; protected set; }
		public string SqlCommand { get { return this.StringHeap.GetString(SqlCommandInt32); } }
		public Int32 SqlCommandInt32 { get; protected set; }
		public string SqlParameters { get { return this.StringHeap.GetString(SqlParametersInt32); } }
		public Int32 SqlParametersInt32 { get; protected set; }
        public int ResultRowCount { get; protected set; }
		public string ResultScalar { get { return this.StringHeap.GetString(ResultScalarInt32); } }
		public Int32 ResultScalarInt32 { get; protected set; }
        public string FirstSqlWord { get { return this.StringHeap.GetString(FirstSqlWordInt32); } protected set { FirstSqlWordInt32 = this.StringHeap.GetInt32(value); } }
		public Int32 FirstSqlWordInt32 { get; protected set; }
		#endregion
        #region Override abstract metody, protected virtual háčky
        /// <summary>
        /// Poté, kdy předek (EventPairInfo) uloží řádek Begin, si my uložíme data SQL
        /// </summary>
        /// <param name="line"></param>
        protected override void StoreLineBeginAfter(TraceItem item, TraceInfo traceInfo)
        {
            // Je zajištěno, že máme 2 položky v poli line.User(xx) (viz zdejší metoda RegisterFor(), kde je podmínkou line.UserLength >= 2)
            int pid;
			if (Int32.TryParse(item.User(0), out pid))
                this.SqlPId = pid;
			this.SqlCommandInt32 = item.UserDataInt32[1];
            this.FirstSqlWord = this.GetFirstSqlWord().ToUpper();

            // Pokud máme i další parametry, uchováme je:
			if (item.UserLength >= 3)
				this.SqlParametersInt32 = item.UserDataInt32[2];
        }
        /// <summary>
        /// Poté, kdy předek (EventPairInfo) uloží řádek End, si my uložíme data SQL
        /// </summary>
        /// <param name="line"></param>
        protected override void StoreLineEndAfter(TraceItem item, TraceInfo traceInfo)
        {
			if (item.UserLength >= 2)
            {
                if (this.Method == "ExecuteScalar")
					this.ResultScalarInt32 = item.UserDataInt32[1];
                else if (this.Method.StartsWith("Fill"))
                {
                    int cnt;
					if (Int32.TryParse(item.User(1), out cnt))
                        this.ResultRowCount = cnt;
                }
            }
        }
        #endregion
        #region Texty do elementu (Caption, Tooltip)
        /// <summary>
        /// Zde potomek vytvoří souhrn textů pro zobrazení uvnitř elementu
        /// </summary>
        /// <returns></returns>
        protected override string[] CreateCaptions()
        {
            string spid = " (SPID: " + this.SqlPId.ToString() + ")";
            string sqlComm = (this.SqlCommand == null ? "" : this.SqlCommand);
            string sqlShr1 = (sqlComm.Length < 25 ? sqlComm : sqlComm.Substring(0, 25) + "...");
            string sqlShr2 = (sqlComm.Length < 75 ? sqlComm : sqlComm.Substring(0, 75) + "...");
            string sqlShr3 = (sqlComm.Length < 150 ? sqlComm : sqlComm.Substring(0, 150) + "...");

            string keyword = (this.Keyword == null ? "" : this.Keyword);
            string type = (this.Type == null ? "" : this.Type);
            string method = (this.Method == null ? "" : this.Method);
            return new string[] 
            {
                this.FirstSqlWord,
                this.FirstSqlWord + spid,
                this.FirstSqlWord + spid + " (" + keyword + ")",
                this.FirstSqlWord + spid + " (" + keyword + ":" + method + ")",
                this.FirstSqlWord + spid + " (" + keyword + ":" + type  + "." + method + ")",
                this.FirstSqlWord + spid + " (" + keyword + ":" + type  + "." + method + ")" + "  " + sqlShr1,
                this.FirstSqlWord + spid + " (" + keyword + ":" + type  + "." + method + ")" + "  " + sqlShr2,
                this.FirstSqlWord + spid + " (" + keyword + ":" + type  + "." + method + ")" + "  " + sqlShr3,
                this.FirstSqlWord + spid + " (" + keyword + ":" + type  + "." + method + ")" + "  " + sqlComm,

            };
        }
        /// <summary>
        /// Zde potomek vytvoří tooltip
        /// </summary>
        /// <returns></returns>
        protected override string CreateToolTip()
        {
            string caption = CreateCaptions()[1];
            return
                caption + Environment.NewLine +
                "Keyword: " + this.Keyword + "; " +
                "Method: " + this.Method + "; " +
                "Type: " + this.Type + Environment.NewLine +
                "Login: " + this.Login + Environment.NewLine +
                "Time: " + this.ElementTime.StringFullDetail + Environment.NewLine +
                Environment.NewLine +
                "ThreadId: " + this.ThreadId.ToString() + "; RoundtripId: " + this.RoundtripId.ToString() + "; PageId: " + this.PageId.ToString() + Environment.NewLine +
                "Trace Begin: " + (this.Trace == null ? "???" : this.Trace.FileDirName) + "; #" + this.LineNumber.ToString() + Environment.NewLine +
                "Trace End: " + (this.TraceEnd == null ? "???" : this.TraceEnd.FileDirName) + "; #" + this.LineNumberEnd.ToString() + Environment.NewLine +
                Environment.NewLine +
                (String.IsNullOrEmpty(this.SqlParameters) ? "" : "SqlParams: " + this.SqlParameters + Environment.NewLine) +
                this.SqlCommandShort;
        }

        internal string SqlCommandShort { get { return TraceSupport.ShortSqlCommandText(this.SqlCommand, 150, 3); } }
        /// <summary>
        /// Metoda má za úkol vrátit klíčové slovo sebe sama, které bude hrát roli v ColorGroup. Slovo má mít na začátku tečku!!!
        /// Metoda se volá jen pro některá klíčová slova.
        /// </summary>
        /// <returns></returns>
        protected override string GetColorGroupKeyword()
        {
            string sql = this.FirstSqlWord;
            if (String.IsNullOrEmpty(sql)) return "";
            return "." + sql.ToLower();
        }
        internal string GetFirstSqlWord()
        {
            string sql = this.SqlCommand;
            if (String.IsNullOrEmpty(sql)) return "";
            sql = sql.Trim();
            int idx = sql.IndexOf(" ");
            if (idx > 0)
                sql = sql.Substring(0, idx).Trim();
            sql = sql.ToLower();
            return sql;
        }
        #endregion
        #region Texty do okna Detail
        protected override string DetailMasterName
        {
            get { return this.LineNumber.ToString() + ": " + this.ElementTime.Begin.ToString("HH:mm:ss.fff") + " + " + this.ElementTime.Seconds.ToString("### ##0.000") + " sec; " + this.FirstSqlWord; }
        }
        protected override void DetailFill(List<MasterDetailItem> result)
        {
            base.DetailFill(result);

            result.Add(new MasterDetailItem("SqlPId", this.SqlPId));
            result.Add(new MasterDetailItem("FirstSqlWord", this.FirstSqlWord));
            result.Add(new MasterDetailItem("SqlCommand", this.SqlCommand));
            result.Add(new MasterDetailItem("SqlParameters", this.SqlParameters));
            result.Add(new MasterDetailItem("ResultRowCount", this.ResultRowCount));
            result.Add(new MasterDetailItem("ResultScalar", this.ResultScalar));

        }
        protected override void DetailFillUserData(List<MasterDetailItem> result)
        {
            // nic
        }
        #endregion
    }
    #endregion
	#region EventCowleyItem : data o události NrsCowley
	/// <summary>
	/// EventCowleyItem : data o události NrsCowley.
    /// Tato událost se postupně merguje z více samostatných událostí (GetCowley, RunPart1, RunPart3, RunPart4, RunPart5).
    /// Ne každá instance tedy obsahuje plná data o funkci.
	/// </summary>
    public class EventCowleyItem : EventPairItem
	{
		#region Static detekce RegisterFor()
		/// <summary>
		/// Zde potomek určí, zda on je vhodný pro daný trace řádek
		/// </summary>
		/// <param name="line"></param>
		/// <returns></returns>
		public static new bool RegisterFor(TraceItem item)
		{
			return ((item.BSE == LineBSE.Begin || item.BSE == LineBSE.End) && item.Keyword == "COWLEY");
		}
		#endregion
        #region Konstrukce, Property specifické pro SQL select
        /// <summary>
        /// Kvůli technice IPlugin musím nabízet alespoň privátní bezparametrický konstruktor:
        /// </summary>
        protected EventCowleyItem() { }
        /// <summary>
        /// Standardní konstruktor
        /// </summary>
        /// <param name="stringHeap"></param>
        /// <param name="item"></param>
        protected EventCowleyItem(StringHeap stringHeap, TraceItem item)
            : base(stringHeap, item)
		{
            this.MergeKey = "cowley" + "_" + item.PageId.ToString();
		}
		public static EventItem CreateEvent(StringHeap stringHeap, TraceItem item)
        {
            EventCowleyItem info = new EventCowleyItem(stringHeap, item);
            return info;
        }
		public override string ToString()
		{
			return "Cowley: ID=" + this.CowleyFunctionNumber.ToString() + ": " + this.CowleyName + "; Time=" + this.Time.StringBeginAndSecs;
		}
        /// <summary>
        /// Číslo Noris třídy
        /// </summary>
		public int CowleyClassNumber { get; protected set; }
        /// <summary>
        /// Číslo funkce
        /// </summary>
        public int CowleyFunctionNumber { get; protected set; }
        /// <summary>
        /// Plný název Type Cowley
        /// </summary>
		public string CowleyTypeFullName { get { return this.StringHeap.GetString(CowleyTypeFullNameInt32); } set { CowleyTypeFullNameInt32 = this.StringHeap.GetInt32(value); } }
		public Int32 CowleyTypeFullNameInt32 { get; protected set; }
        /// <summary>
        /// Akce funkce (název metody = název Cowley v repozitory třídy)
        /// </summary>
		public string CowleyActionName { get { return this.StringHeap.GetString(CowleyActionNameInt32); } set { CowleyActionNameInt32 = this.StringHeap.GetInt32(value); } }
		public Int32 CowleyActionNameInt32 { get; protected set; }
        /// <summary>
        /// Pořadač spuštění funkce
        /// </summary>
		public string CowleyFolderNumber { get { return this.StringHeap.GetString(CowleyFolderNumberInt32); } set { CowleyFolderNumberInt32 = this.StringHeap.GetInt32(value); } }
		public Int32 CowleyFolderNumberInt32 { get; protected set; }
        /// <summary>
        /// Textový uživatelský název funkce
        /// </summary>
		public string CowleyName { get { return this.StringHeap.GetString(CowleyNameInt32); } set { CowleyNameInt32 = this.StringHeap.GetInt32(value); } }
		public Int32 CowleyNameInt32 { get; protected set; }
		public string CowleyInteractive { get { return this.StringHeap.GetString(CowleyInteractiveInt32); } set { CowleyInteractiveInt32 = this.StringHeap.GetInt32(value); } }
		public Int32 CowleyInteractiveInt32 { get; protected set; }
		public string CowleyInner { get { return this.StringHeap.GetString(CowleyInnerInt32); } set { CowleyInnerInt32 = this.StringHeap.GetInt32(value); } }
		public Int32 CowleyInnerInt32 { get; protected set; }
		public string CowleyTransaction { get { return this.StringHeap.GetString(CowleyTransactionInt32); } set { CowleyTransactionInt32 = this.StringHeap.GetInt32(value); } }
		public Int32 CowleyTransactionInt32 { get; protected set; }
		public int CowleyRecordNumbers { get; protected set; }
		/// <summary>
		/// Úroveň této události, řídí režim mergování:
		/// Po sobě jdoucí události typu Cowley mají zvyšující se úrovně (GetCowley = 1, Part1 = 2, ... Part5 = 6).
		/// Mergovat lze pouze vyšší do nižší, jakmile je po některé vyšší úrovni nalezena příští událost s nižší úrovní, zahajuje nový Cowley.
		/// </summary>
		protected int CowleyThisLevel { get; set; }
		/// <summary>
		/// Úroveň dosud nejvyšší jiné události přimergované do této. Výchozí je 0.
		/// </summary>
		protected int CowleyLastMergedLevel { get; set; }
		#endregion
		#region Override metody a property
		/// <summary>
		/// Poté, kdy předek (EventPairInfo) uloží řádek Begin, si my uložíme data SQL
		/// </summary>
		/// <param name="line"></param>
        protected override void StoreLineBeginAfter(TraceItem item, TraceInfo traceInfo)
		{
			// Sem chodí více událostí, liší se metodou:
			int number;
			switch (item.Method.ToLower())
			{
				case "_getcowley":
					this.CowleyThisLevel = 1;
					if (item.UserLength >= 2)
					{
						if (Int32.TryParse(item.User(0), out number))
							this.CowleyClassNumber = number;
						this.CowleyTypeFullNameInt32 = item.UserDataInt32[1];
					}
					if (item.UserLength >= 3)
						this.CowleyActionNameInt32 = item.UserDataInt32[2];
					if (item.UserLength >= 4)
						this.CowleyFolderNumberInt32 = item.UserDataInt32[3];
					break;
				case "_run1_nonvisualpart":
					this.CowleyThisLevel = 2;
					if (item.UserLength >= 2)
					{
						if (Int32.TryParse(item.User(0), out number))
							this.CowleyClassNumber = number;
						this.CowleyTypeFullNameInt32 = item.UserDataInt32[1];
					}
					if (item.UserLength >= 3)
					{
						if (Int32.TryParse(item.User(2), out number))
							this.CowleyFunctionNumber = number;
					}
					if (item.UserLength >= 4)
						this.CowleyInteractiveInt32 = item.UserDataInt32[3];
					if (item.UserLength >= 5)
						this.CowleyTypeFullNameInt32 = item.UserDataInt32[4];
					if (item.UserLength >= 6)
						this.CowleyNameInt32 = item.UserDataInt32[5];
					break;

				case "_run2_progressbarpart":
					this.CowleyThisLevel = 3;
					break;

				case "_run3_rundonepart":
					this.CowleyThisLevel = 4;
					if (item.UserLength >= 2)
					{
						if (Int32.TryParse(item.User(0), out number))
							this.CowleyClassNumber = number;
						this.CowleyActionNameInt32 = item.UserDataInt32[1];
					}
					if (item.UserLength >= 3)
						this.CowleyInteractiveInt32 = item.UserDataInt32[2];
					if (item.UserLength >= 4)
						this.CowleyInnerInt32 = item.UserDataInt32[3];
					if (item.UserLength >= 5)
						this.CowleyTransactionInt32 = item.UserDataInt32[4];
					break;

				case "_run5_invokepart":
					this.CowleyThisLevel = 6;
					if (item.UserLength >= 2)
					{
						if (Int32.TryParse(item.User(0), out number))
							this.CowleyClassNumber = number;
						this.CowleyActionNameInt32 = item.UserDataInt32[1];
					}
					if (item.UserLength >= 3)
						this.CowleyInteractiveInt32 = item.UserDataInt32[2];
					if (item.UserLength >= 5)
					{
						if (Int32.TryParse(item.User(0), out number))
							this.CowleyRecordNumbers = number;
					}
					break;
			}
		}
		/// <summary>
		/// Poté, kdy předek (EventPairInfo) uloží řádek End, si my uložíme data SQL
		/// </summary>
		/// <param name="line"></param>
        protected override void StoreLineEndAfter(TraceItem item, TraceInfo traceInfo)
		{
		}
		#endregion
        #region Texty do elementu (Caption, Tooltip)
        /// <summary>
        /// Zde potomek vytvoří souhrn textů pro zobrazení uvnitř elementu
        /// </summary>
        /// <returns></returns>
        protected override string[] CreateCaptions()
        {
            string fn = (this.CowleyFunctionNumber > 0 ? this.CowleyFunctionNumber.ToString() : "");        // "1234"     nebo ""
            string fnd = (fn == "" ? "" : fn + ": ");                                                       // "1234: "   nebo ""
            return new string[] 
            {
                fn,
                fnd + this.CowleyActionName,
                fnd + this.CowleyName + " (" + this.CowleyActionName + ")",
                fnd + this.CowleyName + " (" + this.CowleyTypeFullName + "." + this.CowleyActionName + "() )"
            };
        }
        /// <summary>
        /// Zde potomek vytvoří tooltip
        /// </summary>
        /// <returns></returns>
        protected override string CreateToolTip()
        {
            return
                CaptionMaxima +
                "Cowley: " + this.CowleyFunctionNumber.ToString() + ": " + this.CowleyName + Environment.NewLine +
                "In code: " + this.CowleyTypeFullName + "." + this.CowleyActionName + "()" + Environment.NewLine +
                "Folder: " + this.CowleyFolderNumber + ", ClassNumber: " + this.CowleyRecordNumbers + Environment.NewLine +
                "Info: " + this.CowleyRunInfo + Environment.NewLine +
                "Time: " + this.ElementTime.StringFullDetail + Environment.NewLine +
                Environment.NewLine +
                "ThreadId: " + this.ThreadId.ToString() + "; RoundtripId: " + this.RoundtripId.ToString() + "; PageId: " + this.PageId.ToString() + Environment.NewLine +
                "Trace Begin: " + (this.Trace == null ? "???" : this.Trace.FileDirName) + "; #" + this.LineNumber.ToString() + Environment.NewLine +
                "Trace End: " + (this.TraceEnd == null ? "???" : this.TraceEnd.FileDirName) + "; #" + this.LineNumberEnd.ToString() + Environment.NewLine;

        }
        internal string CowleyRunInfo
        {
            get
            {
                string result = "";
                if (!String.IsNullOrEmpty(this.CowleyInteractive))
                    result += this.CowleyInteractive + "; ";

                if (!String.IsNullOrEmpty(this.CowleyInner))
                    result += this.CowleyInner + "; ";

                if (!String.IsNullOrEmpty(this.CowleyTransaction))
                    result += this.CowleyTransaction + "; ";

                return result;
            }
        }
        #endregion
        #region Texty do okna Detail
        protected override string DetailMasterName
        {
            get { return this.LineNumber.ToString() + ": " + this.ElementTime.Begin.ToString("HH:mm:ss.fff") + " + " + this.ElementTime.Seconds.ToString("### ##0.000") + " sec; " + this.CowleyActionName; }
        }
        protected override void DetailFill(List<MasterDetailItem> result)
        {
            base.DetailFill(result);

            result.Add(new MasterDetailItem("CowleyClassNumber", this.CowleyClassNumber));
            result.Add(new MasterDetailItem("CowleyFunctionNumber", this.CowleyFunctionNumber));
            result.Add(new MasterDetailItem("CowleyTypeFullName", this.CowleyTypeFullName));
            result.Add(new MasterDetailItem("CowleyActionName", this.CowleyActionName));
            result.Add(new MasterDetailItem("CowleyFolderNumber", this.CowleyFolderNumber));
            result.Add(new MasterDetailItem("CowleyName", this.CowleyName));
            result.Add(new MasterDetailItem("CowleyRecordNumbers", this.CowleyRecordNumbers));
        }
        protected override void DetailFillUserData(List<MasterDetailItem> result)
        {
            // nic
        }
        #endregion
        #region Podpora mergování elementů
		/// <summary>
		/// Dotaz, zda lze položku nextEvent připojit do položky this (pak by položka this byla výsledkem, do něhož se přidá nextEvent).
		/// Toto je pouze detekční metoda, teprve po vrácení hodnoty true budou volány metody MergeInWithThing() a MergeOutIntoThing().
		/// </summary>
		/// <param name="nextEvent">Následující položka</param>
		/// <returns></returns>
		public override bool MergeWith(EventItem nextEvent)
		{
			// Pokud druhá událost není jako my (ThingPairCowleyInfo), pak ji nechci:
			EventCowleyItem nextCowley = nextEvent as EventCowleyItem;
			if (nextCowley == null)
				return false;

			// Máme za sebou dvě události typu NrsCowley, v témže procesu a se shodným klíčem MergeKey (proto se dostalo volání sem, to zajišťuje datová vrstva), a jsou seřazené časově po sobě.
			// Mergovat mohu pouze událost vyšší úrovně do události, v níž je přimergovaná událost nižší. Pokud dostanu něco jiného
            bool mergable = !(nextCowley.CowleyThisLevel <= this.CowleyLastMergedLevel || nextCowley.CowleyThisLevel <= this.CowleyThisLevel);
            if (!mergable && this.ContainMergedEvent(nextCowley))         // Pokud podle pravidel nelze mergovat, ale this už danou událost obsahuje (od dřívějška, nyní probíhá ReMerge),
                mergable = true;                                          //   pak mergovat musíme.
            if (!mergable)
				return false;

			// Další testy, zda jde o stejný cowley:


            // Sloučit:
            Merge(this, nextCowley);

			return true;
		}
        /// <summary>
        /// Zajistí sloučení dvou objektů do jednoho (do toho prev).
        /// </summary>
        /// <param name="prev"></param>
        /// <param name="next"></param>
        internal static void Merge(EventCowleyItem prev, EventCowleyItem next)
        {
            // Sloučení Noris hodnot:
            prev.MergeTrace(next);                      // Určí první a poslední záznam trace, vše dává do prev (ten zůstává). Musí běžet před sloučením času.
            prev.MergeEvent(next);                      // Merguje čas a nejistotu, merguje event next do this

            prev.CowleyLastMergedLevel = next.CowleyThisLevel;
            prev.CowleyClassNumber = MergeValue(prev.CowleyClassNumber, next.CowleyClassNumber);
            prev.CowleyTypeFullName = MergeValue(prev.CowleyTypeFullName, next.CowleyTypeFullName);
            prev.CowleyActionName = MergeValue(prev.CowleyActionName, next.CowleyActionName);
            prev.CowleyFolderNumber = MergeValue(prev.CowleyFolderNumber, next.CowleyFolderNumber);
            prev.CowleyFunctionNumber = MergeValue(prev.CowleyFunctionNumber, next.CowleyFunctionNumber);
            prev.CowleyName = MergeValue(prev.CowleyName, next.CowleyName);
            prev.CowleyInteractive = MergeValue(prev.CowleyInteractive, next.CowleyInteractive);
            prev.CowleyInner = MergeValue(prev.CowleyInner, next.CowleyInner);
            prev.CowleyTransaction = MergeValue(prev.CowleyTransaction, next.CowleyTransaction);
            prev.CowleyRecordNumbers = MergeValue(prev.CowleyRecordNumbers, next.CowleyRecordNumbers);

            // Redefinice barevné skupiny, tak aby obsahovala namespace, objekt a metodu:
            prev.MergeRefreshColorGroup();
        }
        /// <summary>
        /// Aktualizuje si barevnou skupinu poté, kdy do this byla mergována nějaká další událost.
        /// Barevná skupina by optimálně měla znít: NrsCowley.Lcs.Manufacturing.IssueReceiptCwl.Issue()
        /// </summary>
        internal void MergeRefreshColorGroup()
        {
			this.ColorGroup = "NrsCowley." + this.CowleyTypeFullName + "." + this.CowleyActionName + "()";
			this.ColorIndex = this.CowleyFunctionNumber;
			return;


            string nameSpace = this.CowleyTypeFullName;
            if (!nameSpace.IsEmpty())
            {
                string[] nsItms = nameSpace.Split('.');
                nameSpace = "";
                int c = nsItms.Length;
                for (int i = 0; i < (c - 1); i++)              // Položku [0] otestuji, položku poslední neberu = ta obsahuje název třídy cowleyho. Chci separovat jen "Lcs.Manufacturing":
                {
                    string f = nsItms[i];
                    if (i > 0 || !String.Equals(f, "Noris", StringComparison.OrdinalIgnoreCase))
                        nameSpace += (nameSpace.Length == 0 ? "" : ".") + f;
                }
            }
            this.ColorGroup = "NrsCowley." + nameSpace + "." + this.CowleyFunctionNumber.ToString();
            this.ColorIndex = this.CowleyFunctionNumber;
        }
        /// <summary>
        /// Vrátí sloučené dvě hodnoty
        /// </summary>
        /// <param name="prevValue"></param>
        /// <param name="nextValue"></param>
        /// <returns></returns>
        protected static string MergeValue(string prevValue, string nextValue)
        {
            bool prevExists = !String.IsNullOrEmpty(prevValue);
            bool nextExists = !String.IsNullOrEmpty(nextValue);
            if (!prevExists && !nextExists) return null;
            if (prevExists && !nextExists) return prevValue;
            if (!prevExists && nextExists) return nextValue;
            
            if (String.Equals(prevValue.Trim(), nextValue.Trim(), StringComparison.CurrentCultureIgnoreCase)) return prevValue;
            if (prevValue.ToLower().Contains(nextValue.Trim().ToLower())) return prevValue;
            return prevValue + Environment.NewLine + nextValue;
        }
        /// <summary>
        /// Vrátí sloučené dvě hodnoty
        /// </summary>
        /// <param name="prevValue"></param>
        /// <param name="nextValue"></param>
        /// <returns></returns>
        protected static int MergeValue(int prevValue, int nextValue)
        {
 	        bool prevExists = prevValue != 0;
            bool nextExists = nextValue != 0;
            if (!prevExists && !nextExists) return 0;
            if (prevExists && !nextExists) return prevValue;
            if (!prevExists && nextExists) return nextValue;
            
            return prevValue;
        }
		#endregion
    }
	#endregion
	#region Enums StatusInfoType, TransactionChangeType.
    /// <summary>
    /// Obdobný základní význam jako LineIWE. 
    /// Ale některé stavy detekuje přesněji, například Deadlock.
    /// Navíc definuje stav Event, který není Info (běžné události Begin / End).
    /// </summary>
    public enum StatusInfoType
    {
        None = 0,
        /// <summary>
        /// Běžná událost, ani ne Informace
        /// </summary>
        Event,
        /// <summary>
        /// System.Info
        /// </summary>
        Information,
        /// <summary>
        /// System.Warning
        /// </summary>
        Warning,
        /// <summary>
        /// System.Error bez podrobnějšího určení
        /// </summary>
        Error,
        /// <summary>
        /// Sql.Deadlock
        /// </summary>
        Deadlock
    }
    public enum TransactionChangeType
	{
		None = 0,
        CheckState,
		Begin,
		Commit,
		End,
		Rollback
	}
	#endregion
}
